sheets_v4 0.1.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e8b306cf08149067ba4f5506c54c88df4e2af706f064b3b2b7cc7f5919991f4
4
- data.tar.gz: 438a64d88598542b9f9ed56552077836089e04b411aa8a4ceabf5279b131a393
3
+ metadata.gz: a6357385eeb33468356dd974f8fda49f92e09b30b9911a825c562509bf5f70dd
4
+ data.tar.gz: a289b95495fd49e8221a89e037168aaae903ca40af2f9167cff60c75c0f28acc
5
5
  SHA512:
6
- metadata.gz: 3728b1539d987a5aa7dfbbb45d0a9637d01c39d5fe3d8a3e19cd7206b9fab4b9ce1458d1f6e534e6ddb79d93d2792c93f940f3a800f2eb657647dd4dfdebceb9
7
- data.tar.gz: fe7d8f59b7663f76804d18e13ece431bdce418252efe51a2e50d28a5d3e5fe1c979b367287155032e82571e09c593bd9be2b40cc461901a4bf5bec728cfcc8e2
6
+ metadata.gz: 59b81fbfa525ee8f5810d1aeeb3d6168dfffe0ffd1a8c19792d8f27548cdb1b2106748a30f499f8619bd83cd73d773c5647f0b7fd57640c990af2eb7e9d5fd7b
7
+ data.tar.gz: '09ffa389430c102e6e4df4370bc42f2e8769399f78cabbfe61441372ed6bf4a87378a124a3c17fe0c7f53a73255fae7f153cd08df67e93589c5bcbb49360166e'
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
@@ -23,8 +23,12 @@ Metrics/BlockLength:
23
23
  - "spec/**/*_spec.rb"
24
24
  - "*.gemspec"
25
25
 
26
+ Metrics/ModuleLength:
27
+ CountAsOne: ['hash']
28
+
26
29
  # When writing minitest tests, it is very hard to limit test class length:
27
30
  Metrics/ClassLength:
31
+ CountAsOne: ['hash']
28
32
  Exclude:
29
33
  - "test/**/*_test.rb"
30
34
 
data/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ 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.4.0 (2023-09-29)
8
+
9
+ [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.3.0..v0.4.0)
10
+
11
+ Changes since v0.3.0:
12
+
13
+ * 843c1b5 Add SheetsV4.sheets_service (#13)
14
+ * 16a73f5 Refactor ValidateApiObject (#12)
15
+ * 1525cd3 Add a link in the README to the Sheet V4 API Discover Doc (#11)
16
+ * 6d5bfb3 Improve color api and documentation (#10)
17
+
18
+ ## v0.3.0 (2023-09-28)
19
+
20
+ [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.2.0..v0.3.0)
21
+
22
+ Changes since v0.2.0:
23
+
24
+ * 0fb4579 Add predefined color objects (#8)
25
+
26
+ ## v0.2.0 (2023-09-26)
27
+
28
+ [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.1.1..v0.2.0)
29
+
30
+ Changes since v0.1.1:
31
+
32
+ * 489ff50 Add SheetsV4.validate_api_object (#6)
33
+ * b44eba4 List all examples that should be created (#5)
34
+ * 2edd688 Add links to Google Sheets documentation to the README.md (#4)
35
+
7
36
  ## v0.1.1 (2023-09-22)
8
37
 
9
38
  [Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.1.0..v0.1.1)
data/README.md CHANGED
@@ -9,12 +9,36 @@
9
9
 
10
10
  Unofficial helpers for the Google Sheets V4 API
11
11
 
12
- - [SheetsV4](#sheetsv4)
13
- - [Installation](#installation)
14
- - [Usage](#usage)
15
- - [Development](#development)
16
- - [Contributing](#contributing)
17
- - [License](#license)
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
+ * [Obtaining an authenticated SheetsService](#obtaining-an-authenticated-sheetsservice)
19
+ * [Colors](#colors)
20
+ * [Development](#development)
21
+ * [Creating a Google API Service Account](#creating-a-google-api-service-account)
22
+ * [Contributing](#contributing)
23
+ * [License](#license)
24
+
25
+ ## Important Links for Programming Google Sheets
26
+
27
+ ### General API Documentation
28
+
29
+ * [Google Sheets API Overview](https://developers.google.com/sheets/api)
30
+ * [Google Sheets API Reference](https://developers.google.com/sheets/api/reference/rest)
31
+ * [Batch Update Requests](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request)
32
+ * [Discovery Document for the Sheets API](https://sheets.googleapis.com/$discovery/rest?version=v4)
33
+
34
+ ### Ruby Implementation of the Sheets API
35
+
36
+ * [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
+ * [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)
38
+
39
+ ### Other Links
40
+
41
+ * [Apps Script for Sheets](https://developers.google.com/apps-script/guides/sheets)
18
42
 
19
43
  ## Installation
20
44
 
@@ -34,6 +58,60 @@ gem install sheets_v4
34
58
 
35
59
  [Detailed API documenation](https://rubydoc.info/gems/sheets_v4/) is hosted on rubygems.org.
36
60
 
61
+ ### Obtaining an authenticated SheetsService
62
+
63
+ Typically, to construct an authenticated SheetsService object where the credential
64
+ is read from a file, the following code is required:
65
+
66
+ ```Ruby
67
+ sheets_service = Google::Apis::SheetsV4::SheetsService.new
68
+ credential = File.read(File.expand_path('~/google-api-credential.json')) do |credential_source|
69
+ scopes = Google::Apis::SheetsV4::AUTH_SPREADSHEETS
70
+ options = { json_key_io: credential_source, scope: scopes }
71
+ Google::Auth::DefaultCredentials.make_creds(options).tap(&:fetch_access_token)
72
+ end
73
+ sheets_service.authorization = credential
74
+ ```
75
+
76
+ The `SheetsV4.sheets_service` method simplifies this a bit.
77
+
78
+ By default, the credential is read from `~/.google-api-credential.json`. In that case,
79
+ an authenticated SheetsService object can be obtained with one method call:
80
+
81
+ ```Ruby
82
+ sheets_service = SheetsV4.sheets_service
83
+ ```
84
+
85
+ If the credential is stored elsewhere, pass the credential_source to `SheetsV4.sheets_service`
86
+ manually. `credential_source` can be a String:
87
+
88
+ ```Ruby
89
+ sheets_service = SheetsV4.sheets_service(credential_sourvce: File.read('credential.json'))
90
+ ```
91
+
92
+ an IO object:
93
+
94
+ ```Ruby
95
+ sheets_service = File.open('credential.json') do |credential_source|
96
+ SheetsV4.sheets_service(credential_sourvce:)
97
+ end
98
+ ```
99
+
100
+ or an already constructed `Google::Auth::*`` object.
101
+
102
+ ### Colors
103
+
104
+ Color objects (with appropriate :red, :green, :blue values) can be retrieved by name
105
+ using `SheetsV4.color(:black)` or `SheetsV4::Color.black` (these are equivalent).
106
+
107
+ Color names can be listed using `SheetsV4.color_names`.
108
+
109
+ The color object returned is a Hash as follows:
110
+
111
+ ```Ruby
112
+ SheetsV4::Color.black #=> {:red=>0.0, :green=>0.0, :blue=>0.0}
113
+ ```
114
+
37
115
  ## Development
38
116
 
39
117
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -46,6 +124,9 @@ release a new version, update the version number in `version.rb`, and then run
46
124
  commits and the created tag, and push the `.gem` file to
47
125
  [rubygems.org](https://rubygems.org).
48
126
 
127
+ ## Creating a Google API Service Account
128
+
129
+
49
130
  ## Contributing
50
131
 
51
132
  Bug reports and pull requests are welcome on [the main-branch/sheets_v4 GitHub project](https://github.com/main-branch/sheets_v4).
@@ -53,4 +134,4 @@ Bug reports and pull requests are welcome on [the main-branch/sheets_v4 GitHub p
53
134
  ## License
54
135
 
55
136
  The gem is available as open source under the terms of the
56
- [MIT License](https://opensource.org/licenses/MIT).
137
+ [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 yard:audit yard:coverage bundle:audit build]
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
- # YARD
61
+ # yard:build
62
62
 
63
63
  require 'yard'
64
- YARD::Rake::YardocTask.new do |t|
65
- t.files = %w[lib/**/*.rb examples/**/*]
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
- # Yardstick
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
- # Yardstick coverage
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
@@ -0,0 +1,48 @@
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
+ * [X] Set background color
7
+ * [ ] Creating Google Sheets files [1](https://www.youtube.com/watch?v=JRUxeQ6ZCy0&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=2)
8
+ * [ ] Writing data to a sheet [1](https://www.youtube.com/watch?v=YF7Ad-7pvks&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=3)
9
+ * [ ] Reading data from a sheet [1](https://www.youtube.com/watch?v=gkglr8GID5E&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=4)
10
+ * Reading data
11
+ * Reading formulas
12
+ * [ ] Data formatting basics [1](https://www.youtube.com/watch?v=R4EN3iPRris&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=5)
13
+ * [ ] Creating charts [1](https://www.youtube.com/watch?v=xt3p5I8mNWE&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=6)
14
+ * [ ] Data validation [1](https://www.youtube.com/watch?v=n_Z2565gu6Y&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=7)
15
+ * [ ] Cut, copy, and paste [1](https://www.youtube.com/watch?v=r8GWH2E_ehw&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=8)
16
+ * [ ] Duplicate sheets [1](https://www.youtube.com/watch?v=BgQoPcoOiGY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=9)
17
+ * [ ] List sheets in a spreadsheet [1](https://www.youtube.com/watch?v=BgQoPcoOiGY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=9)
18
+ * [ ] Set column width and row height [1](https://www.youtube.com/watch?v=H3uMEaPqTVE&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=11)
19
+ * [ ] Append rows and columns [1](https://www.youtube.com/watch?v=txfiwEjb7sk&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=12)
20
+ * [ ] Delete rows and columns [1](https://www.youtube.com/watch?v=w1jrCxWx7Tc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=13)
21
+ * [ ] Insert rows and columns [1](https://www.youtube.com/watch?v=FL7WSsO5EVs&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=14)
22
+ * [ ] Move rows and columns [1](https://www.youtube.com/watch?v=YHk3305dkOc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=15)
23
+ * [ ] Clear data [1](https://www.youtube.com/watch?v=mvbnhfdDrro&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=16)
24
+ * [ ] Add and delete sheets [1](https://www.youtube.com/watch?v=X9PVQQVoJFc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=17)
25
+ * [ ] Copy sheet from one spreadsheet to another [1](https://www.youtube.com/watch?v=aIEM7Ts4n-c&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=18)
26
+ * [ ] Add a new sheet to an existing spreadsheet
27
+ * [ ] Export a sheet to a CSV file [1](https://www.youtube.com/watch?v=Dz22fsWsLhI&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=25)
28
+ * [ ] Sort sheets [1](https://www.youtube.com/watch?v=qbBZX7uBM1M&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=27)
29
+ * [ ] Add calculated fields into a pivot table [1](https://www.youtube.com/watch?v=VR8zOz5ATLU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=32)
30
+ * [ ] Named ranges [1](https://www.youtube.com/watch?v=LTPdfXS_LHA&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=42)
31
+ * [ ] Create a pivot table [1](https://www.youtube.com/watch?v=preFnuL7ua0&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=28)
32
+ * [ ] Calculated pivot fields [1](https://www.youtube.com/watch?v=QLssI4uvjk4&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=33)
33
+ * [ ] Delete a pivot table
34
+ * [ ] Add pivot fields [1](https://www.youtube.com/watch?v=VR8zOz5ATLU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=32)
35
+ * [ ] Add pivot filters [1](https://www.youtube.com/watch?v=EKikw-eIcbY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=29)
36
+ * [ ] Collapse/expand pivot table groups [1](https://www.youtube.com/watch?v=-S9bs5-ZJ5E&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=31)
37
+ * [ ] Extract pivot table metadata [1](https://www.youtube.com/watch?v=H1SGdqbaL4w&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=30)
38
+ * [ ] Filter views [1](https://www.youtube.com/watch?v=GyRxsSlx0GU&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=34)
39
+ * [ ] Locate the last row in a column [1](https://www.youtube.com/watch?v=NWWHleJll28&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=35)
40
+ * [ ] Autofill [1](https://www.youtube.com/watch?v=guHGNmODdpM&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=36)
41
+ * [ ] Rename a sheet [1](https://www.youtube.com/watch?v=iuiDUJ4NrQI&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=37)
42
+ * [ ] Find and replace [1](https://www.youtube.com/watch?v=YaFR0bu5CrY&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=39)
43
+ * [ ] Add and delete sheets [1](https://www.youtube.com/watch?v=gMD4v8F8vlc&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=38)
44
+ * [ ] Sum across sheets [1](https://www.youtube.com/watch?v=7QNk-MXkPC4&list=PL3JVwFmb_BnSee8RFaRPZ3nykuMRlaQp1&index=44)
45
+ * [ ] Freeze rows / columns
46
+ * [ ] Protected ranges
47
+ * [ ] Resize a sheet
48
+ * [ ] Retrying on error
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'sheets_v4'
5
+ require 'googleauth'
6
+
7
+ # Example to show using the SheetsV4 module to set the background color of a cell
8
+ #
9
+ # GIVEN the credential file is at ~/.google-api-credential.json
10
+ # AND the spreadsheet id is 18FAcgotK7nDfLTOTQuGCIjKwxkJMAguhn1OVzpFFgWY
11
+ # AND the spreadsheet has a sheet whose id is 0
12
+ # WHEN the script is run
13
+ # THEN the sheet whose id is 0 will have the background color names starting at A2
14
+ # AND the background color of the cells in column B will be set to the color matching the color name in column A
15
+
16
+ # Build the requests
17
+
18
+ def name_rows
19
+ SheetsV4.color_names.map do |color_name|
20
+ { values: [{ user_entered_value: { string_value: color_name.to_s } }] }
21
+ end
22
+ end
23
+
24
+ def write_names
25
+ rows = name_rows
26
+ fields = 'user_entered_value'
27
+ start = { sheet_id: 0, row_index: 1, column_index: 0 }
28
+ { update_cells: { rows:, fields:, start: } }
29
+ end
30
+
31
+ def background_color_rows
32
+ SheetsV4.color_names.map { |color_name| SheetsV4.color(color_name) }.map do |color|
33
+ { values: [{ user_entered_format: { background_color: color } }] }
34
+ end
35
+ end
36
+
37
+ def set_background_colors
38
+ rows = background_color_rows
39
+ fields = 'user_entered_format'
40
+ start = { sheet_id: 0, row_index: 1, column_index: 1 }
41
+ { update_cells: { rows:, fields:, start: } }
42
+ end
43
+
44
+ def requests = { requests: [write_names, set_background_colors] }
45
+
46
+ # SheetsV4.validate_api_object(
47
+ # schema_name: 'BatchUpdateSpreadsheetRequest', object: request,
48
+ # logger: Logger.new(STDOUT, level: Logger::ERROR)
49
+ # )
50
+
51
+ spreadsheet_id = '18FAcgotK7nDfLTOTQuGCIjKwxkJMAguhn1OVzpFFgWY'
52
+ SheetsV4.sheets_service.batch_update_spreadsheet(spreadsheet_id, requests)
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json_schemer'
4
+
5
+ module SheetsV4
6
+ # Returns predefined color objects by name
7
+ #
8
+ # A method is implemented for each color name. The list of available colors is
9
+ # given by `SheetsV4.color_names`.
10
+ #
11
+ # @example
12
+ # # Get a color object by name:
13
+ # SheetsV4::Color.black #=> { "red" => 0.0, "green" => 0.0, "blue" => 0.0 }
14
+ #
15
+ # @see SheetsV4.color a method that returns a color object by name
16
+ # @see SheetsV4.color_names an array of predefined color names
17
+ #
18
+ # @api public
19
+ #
20
+ class Color
21
+ class << self
22
+ # Return a color object for the given name from SheetsV4.color_names or call super
23
+ #
24
+ # @param method_name [#to_sym] the name of the color
25
+ # @param arguments [Array] ignored
26
+ # @param block [Proc] ignored
27
+ # @return [Hash] the color object
28
+ # @api private
29
+ def method_missing(method_name, *arguments, &)
30
+ COLORS[method_name.to_sym] || super
31
+ end
32
+
33
+ # Return true if the given method name is a color name or call super
34
+ #
35
+ # @param method_name [#to_sym] the name of the color
36
+ # @param include_private [Boolean] ignored
37
+ # @return [Boolean] true if the method name is a color name
38
+ # @api private
39
+ def respond_to_missing?(method_name, include_private = false)
40
+ COLORS.key?(method_name.to_sym) || super
41
+ end
42
+ end
43
+
44
+ # Colors to use in the Google Sheets API
45
+ COLORS = {
46
+ # Standard Google Sheets colors
47
+ #
48
+ black: { red: 0.00000000000, green: 0.00000000000, blue: 0.00000000000 },
49
+ dark_gray4: { red: 0.26274509804, green: 0.26274509804, blue: 0.26274509804 },
50
+ dark_gray3: { red: 0.40000000000, green: 0.40000000000, blue: 0.40000000000 },
51
+ dark_gray2: { red: 0.60000000000, green: 0.60000000000, blue: 0.60000000000 },
52
+ dark_gray1: { red: 0.71764705882, green: 0.71764705882, blue: 0.71764705882 },
53
+ gray: { red: 0.80000000000, green: 0.80000000000, blue: 0.80000000000 },
54
+ light_gray1: { red: 0.85098039216, green: 0.85098039216, blue: 0.85098039216 },
55
+ light_gray2: { red: 0.93725490196, green: 0.93725490196, blue: 0.93725490196 },
56
+ light_gray3: { red: 0.95294117647, green: 0.95294117647, blue: 0.95294117647 },
57
+ white: { red: 1.00000000000, green: 1.00000000000, blue: 1.00000000000 },
58
+ red_berry: { red: 0.59607843137, green: 0.00000000000, blue: 0.00000000000 },
59
+ red: { red: 1.00000000000, green: 0.00000000000, blue: 0.00000000000 },
60
+ orange: { red: 1.00000000000, green: 0.60000000000, blue: 0.00000000000 },
61
+ yellow: { red: 1.00000000000, green: 1.00000000000, blue: 0.00000000000 },
62
+ green: { red: 0.00000000000, green: 1.00000000000, blue: 0.00000000000 },
63
+ cyan: { red: 0.00000000000, green: 1.00000000000, blue: 1.00000000000 },
64
+ cornflower_blue: { red: 0.29019607843, green: 0.52549019608, blue: 0.90980392157 },
65
+ blue: { red: 0.00000000000, green: 0.00000000000, blue: 1.00000000000 },
66
+ purple: { red: 0.60000000000, green: 0.00000000000, blue: 1.00000000000 },
67
+ magenta: { red: 1.00000000000, green: 0.00000000000, blue: 1.00000000000 },
68
+ light_red_berry3: { red: 0.90196078431, green: 0.72156862745, blue: 0.68627450980 },
69
+ light_red3: { red: 0.95686274510, green: 0.80000000000, blue: 0.80000000000 },
70
+ light_orange3: { red: 0.98823529412, green: 0.89803921569, blue: 0.80392156863 },
71
+ light_yellow3: { red: 1.00000000000, green: 0.94901960784, blue: 0.80000000000 },
72
+ light_green3: { red: 0.85098039216, green: 0.91764705882, blue: 0.82745098039 },
73
+ light_cyan3: { red: 0.81568627451, green: 0.87843137255, blue: 0.89019607843 },
74
+ light_cornflower_blue3: { red: 0.78823529412, green: 0.85490196078, blue: 0.97254901961 },
75
+ light_blue3: { red: 0.81176470588, green: 0.88627450980, blue: 0.95294117647 },
76
+ light_purple3: { red: 0.85098039216, green: 0.82352941176, blue: 0.91372549020 },
77
+ light_magenta3: { red: 0.91764705882, green: 0.81960784314, blue: 0.86274509804 },
78
+ light_red_berry2: { red: 0.86666666667, green: 0.49411764706, blue: 0.41960784314 },
79
+ light_red2: { red: 0.91764705882, green: 0.60000000000, blue: 0.60000000000 },
80
+ light_orange2: { red: 0.97647058824, green: 0.79607843137, blue: 0.61176470588 },
81
+ light_yellow2: { red: 1.00000000000, green: 0.89803921569, blue: 0.60000000000 },
82
+ light_green2: { red: 0.71372549020, green: 0.84313725490, blue: 0.65882352941 },
83
+ light_cyan2: { red: 0.63529411765, green: 0.76862745098, blue: 0.78823529412 },
84
+ light_cornflower_blue2: { red: 0.64313725490, green: 0.76078431373, blue: 0.95686274510 },
85
+ light_blue2: { red: 0.62352941176, green: 0.77254901961, blue: 0.90980392157 },
86
+ light_purple2: { red: 0.70588235294, green: 0.65490196078, blue: 0.83921568627 },
87
+ light_magenta2: { red: 0.83529411765, green: 0.65098039216, blue: 0.74117647059 },
88
+ light_red_berry1: { red: 0.80000000000, green: 0.25490196078, blue: 0.14509803922 },
89
+ light_red1: { red: 0.87843137255, green: 0.40000000000, blue: 0.40000000000 },
90
+ light_orange1: { red: 0.96470588235, green: 0.69803921569, blue: 0.41960784314 },
91
+ light_yellow1: { red: 1.00000000000, green: 0.85098039216, blue: 0.40000000000 },
92
+ light_green1: { red: 0.57647058824, green: 0.76862745098, blue: 0.49019607843 },
93
+ light_cyan1: { red: 0.46274509804, green: 0.64705882353, blue: 0.68627450980 },
94
+ light_cornflower_blue1: { red: 0.42745098039, green: 0.61960784314, blue: 0.92156862745 },
95
+ light_blue1: { red: 0.43529411765, green: 0.65882352941, blue: 0.86274509804 },
96
+ light_purple1: { red: 0.55686274510, green: 0.48627450980, blue: 0.76470588235 },
97
+ light_magenta1: { red: 0.76078431373, green: 0.48235294118, blue: 0.62745098039 },
98
+ dark_red_berry1: { red: 0.65098039216, green: 0.10980392157, blue: 0.00000000000 },
99
+ dark_red1: { red: 0.80000000000, green: 0.00000000000, blue: 0.00000000000 },
100
+ dark_orange1: { red: 0.90196078431, green: 0.56862745098, blue: 0.21960784314 },
101
+ dark_yellow1: { red: 0.94509803922, green: 0.76078431373, blue: 0.19607843137 },
102
+ dark_green1: { red: 0.41568627451, green: 0.65882352941, blue: 0.30980392157 },
103
+ dark_cyan1: { red: 0.27058823529, green: 0.50588235294, blue: 0.55686274510 },
104
+ dark_cornflower_blue1: { red: 0.23529411765, green: 0.47058823529, blue: 0.84705882353 },
105
+ dark_blue1: { red: 0.23921568627, green: 0.52156862745, blue: 0.77647058824 },
106
+ dark_purple1: { red: 0.40392156863, green: 0.30588235294, blue: 0.65490196078 },
107
+ dark_magenta1: { red: 0.65098039216, green: 0.30196078431, blue: 0.47450980392 },
108
+ dark_red_berry2: { red: 0.52156862745, green: 0.12549019608, blue: 0.04705882353 },
109
+ dark_red2: { red: 0.60000000000, green: 0.00000000000, blue: 0.00000000000 },
110
+ dark_orange2: { red: 0.70588235294, green: 0.37254901961, blue: 0.02352941176 },
111
+ dark_yellow2: { red: 0.74901960784, green: 0.56470588235, blue: 0.00000000000 },
112
+ dark_green2: { red: 0.21960784314, green: 0.46274509804, blue: 0.11372549020 },
113
+ dark_cyan2: { red: 0.07450980392, green: 0.30980392157, blue: 0.36078431373 },
114
+ dark_cornflower_blue2: { red: 0.06666666667, green: 0.33333333333, blue: 0.80000000000 },
115
+ dark_blue2: { red: 0.04313725490, green: 0.32549019608, blue: 0.58039215686 },
116
+ dark_purple2: { red: 0.20784313725, green: 0.10980392157, blue: 0.45882352941 },
117
+ dark_magenta2: { red: 0.45490196078, green: 0.10588235294, blue: 0.27843137255 },
118
+ dark_red_berry3: { red: 0.35686274510, green: 0.05882352941, blue: 0.00000000000 },
119
+ dark_red3: { red: 0.40000000000, green: 0.00000000000, blue: 0.00000000000 },
120
+ dark_orange3: { red: 0.47058823529, green: 0.24705882353, blue: 0.01568627451 },
121
+ dark_yellow3: { red: 0.49803921569, green: 0.37647058824, blue: 0.00000000000 },
122
+ dark_green3: { red: 0.15294117647, green: 0.30588235294, blue: 0.07450980392 },
123
+ darn_cyan3: { red: 0.04705882353, green: 0.20392156863, blue: 0.23921568627 },
124
+ dark_cornflower_blue3: { red: 0.10980392157, green: 0.27058823529, blue: 0.52941176471 },
125
+ dark_blue3: { red: 0.02745098039, green: 0.21568627451, blue: 0.38823529412 },
126
+ dark_purple3: { red: 0.12549019608, green: 0.07058823529, blue: 0.30196078431 },
127
+ dark_magenta3: { red: 0.29803921569, green: 0.06666666667, blue: 0.18823529412 },
128
+
129
+ # Yahoo brand colors
130
+ #
131
+ grape_jelly: { red: 0.37647058824, green: 0.00392156863, blue: 0.82352941176 },
132
+ hulk_pants: { red: 0.49411764706, green: 0.12156862745, blue: 1.00000000000 },
133
+ malbec: { red: 0.22352941176, green: 0.00000000000, blue: 0.49019607843 },
134
+ tumeric: { red: 1.00000000000, green: 0.65490196078, blue: 0.00000000000 },
135
+ mulah: { red: 0.10196078431, green: 0.77254901961, blue: 0.40392156863 },
136
+ dory: { red: 0.05882352941, green: 0.41176470588, blue: 1.00000000000 },
137
+ malibu: { red: 1.00000000000, green: 0.00000000000, blue: 0.50196078431 },
138
+ sea_foam: { red: 0.06666666667, green: 0.82745098039, blue: 0.80392156863 },
139
+ tumeric_tint: { red: 0.98039215686, green: 0.86666666667, blue: 0.69411764706 },
140
+ mulah_tint: { red: 0.73333333333, green: 0.90196078431, blue: 0.77647058824 },
141
+ dory_tint: { red: 0.66274509804, green: 0.77254901961, blue: 0.98431372549 },
142
+ malibu_tint: { red: 0.96862745098, green: 0.68235294118, blue: 0.80000000000 },
143
+ sea_foam_tint: { red: 0.74901960784, green: 0.92549019608, blue: 0.92156862745 },
144
+
145
+ # Yahoo health colors
146
+ #
147
+ health_green: { red: 0.00000000000, green: 0.69019607843, blue: 0.31372549020 },
148
+ health_yellow: { red: 1.00000000000, green: 0.65490196078, blue: 0.00000000000 },
149
+ health_red: { red: 1.00000000000, green: 0.00000000000, blue: 0.00000000000 },
150
+
151
+ # Yahoo Fuji design color palette
152
+ #
153
+ fuji_color_watermelon: { red: 1.00000000000, green: 0.32156862745, blue: 0.34117647059 },
154
+ fuji_color_solo_cup: { red: 0.92156862745, green: 0.05882352941, blue: 0.16078431373 },
155
+ fuji_color_malibu: { red: 1.00000000000, green: 0.00000000000, blue: 0.50196078431 },
156
+ fuji_color_barney: { red: 0.80000000000, green: 0.00000000000, blue: 0.54901960784 },
157
+ fuji_color_mimosa: { red: 1.00000000000, green: 0.82745098039, blue: 0.20000000000 },
158
+ fuji_color_turmeric: { red: 1.00000000000, green: 0.65490196078, blue: 0.00000000000 },
159
+ fuji_color_cheetos: { red: 0.99215686275, green: 0.38039215686, blue: 0.00000000000 },
160
+ fuji_color_carrot_juice: { red: 1.00000000000, green: 0.32156862745, blue: 0.05098039216 },
161
+ fuji_color_mulah: { red: 0.10196078431, green: 0.77254901961, blue: 0.40392156863 },
162
+ fuji_color_bonsai: { red: 0.00000000000, green: 0.52941176471, blue: 0.31764705882 },
163
+ fuji_color_spirulina: { red: 0.00000000000, green: 0.62745098039, blue: 0.62745098039 },
164
+ fuji_color_sea_foam: { red: 0.06666666667, green: 0.82745098039, blue: 0.80392156863 },
165
+ fuji_color_peeps: { red: 0.49019607843, green: 0.79607843137, blue: 1.00000000000 },
166
+ fuji_color_sky: { red: 0.07058823529, green: 0.66274509804, blue: 1.00000000000 },
167
+ fuji_color_dory: { red: 0.05882352941, green: 0.41176470588, blue: 1.00000000000 },
168
+ fuji_color_scooter: { red: 0.00000000000, green: 0.38823529412, blue: 0.92156862745 },
169
+ fuji_color_cobalt: { red: 0.00000000000, green: 0.22745098039, blue: 0.73725490196 },
170
+ fuji_color_denim: { red: 0.10196078431, green: 0.05098039216, blue: 0.67058823529 },
171
+ fuji_color_blurple: { red: 0.36470588235, green: 0.36862745098, blue: 1.00000000000 },
172
+ fuji_color_hendrix: { red: 0.97254901961, green: 0.95686274510, blue: 1.00000000000 },
173
+ fuji_color_thanos: { red: 0.56470588235, green: 0.48627450980, blue: 1.00000000000 },
174
+ fuji_color_starfish: { red: 0.46666666667, green: 0.34901960784, blue: 1.00000000000 },
175
+ fuji_color_hulk_pants: { red: 0.49411764706, green: 0.12156862745, blue: 1.00000000000 },
176
+ fuji_color_grape_jelly: { red: 0.37647058824, green: 0.00392156863, blue: 0.82352941176 },
177
+ fuji_color_mulberry: { red: 0.31372549020, green: 0.08235294118, blue: 0.69019607843 },
178
+ fuji_color_malbec: { red: 0.22352941176, green: 0.00000000000, blue: 0.49019607843 },
179
+ fuji_grayscale_black: { red: 0.00000000000, green: 0.00000000000, blue: 0.00000000000 },
180
+ fuji_grayscale_midnight: { red: 0.06274509804, green: 0.08235294118, blue: 0.09411764706 },
181
+ fuji_grayscale_inkwell: { red: 0.11372549020, green: 0.13333333333, blue: 0.15686274510 },
182
+ fuji_grayscale_batcave: { red: 0.13725490196, green: 0.16470588235, blue: 0.19215686275 },
183
+ fuji_grayscale_ramones: { red: 0.17254901961, green: 0.21176470588, blue: 0.24705882353 },
184
+ fuji_grayscale_charcoal: { red: 0.27450980392, green: 0.30588235294, blue: 0.33725490196 },
185
+ fuji_grayscale_battleship: { red: 0.35686274510, green: 0.38823529412, blue: 0.41568627451 },
186
+ fuji_grayscale_dolphin: { red: 0.43137254902, green: 0.46666666667, blue: 0.50196078431 },
187
+ fuji_grayscale_shark: { red: 0.50980392157, green: 0.54117647059, blue: 0.57647058824 },
188
+ fuji_grayscale_gandalf: { red: 0.59215686275, green: 0.61960784314, blue: 0.65882352941 },
189
+ fuji_grayscale_bob: { red: 0.69019607843, green: 0.72549019608, blue: 0.75686274510 },
190
+ fuji_grayscale_pebble: { red: 0.78039215686, green: 0.80392156863, blue: 0.82352941176 },
191
+ fuji_grayscale_dirty_seagull: { red: 0.87843137255, green: 0.89411764706, blue: 0.91372549020 },
192
+ fuji_grayscale_grey_hair: { red: 0.94117647059, green: 0.95294117647, blue: 0.96078431373 },
193
+ fuji_grayscale_marshmallow: { red: 0.96078431373, green: 0.97254901961, blue: 0.98039215686 },
194
+ fuji_grayscale_white: { red: 1.00000000000, green: 1.00000000000, blue: 1.00000000000 }
195
+ }.freeze.each_value(&:freeze)
196
+ end
197
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json_schemer'
4
+
5
+ module SheetsV4
6
+ # Creates a Google API credential with an access token
7
+ #
8
+ class CredentialCreator
9
+ # Creates a Google API credential with an access token
10
+ #
11
+ # This wraps the boiler plate code into one function to make constructing a
12
+ # credential easy and less error prone.
13
+ #
14
+ # @example Constructing a credential from the contents of ~/.credential
15
+ # credential_source = File.read(File.join(Dir.home, '.credential'))
16
+ # scope = Google::Apis::SheetsV4::AUTH_SPREADSHEETS
17
+ # credential = GoogleApisHelpers.credential(credential_source, scope)
18
+ #
19
+ # @param [Google::Auth::*, String, IO, nil] credential_source may be one of four things:
20
+ # (1) a previously created credential that you want to reuse, (2) a credential read
21
+ # into a string, (3) an IO object with the credential ready to be read, or (4)
22
+ # if nill, the credential is read from ~/.google-api-credential.json
23
+ # @param scopes [Object, Array] one or more scopes to access.
24
+ # @param credential_factory [#make_creds] Used inject the credential_factory for tests
25
+ #
26
+ # @return [Object] a credential object with an access token
27
+ #
28
+ def self.call(
29
+ credential_source, scopes, credential_factory = Google::Auth::DefaultCredentials
30
+ )
31
+ return credential_source if credential?(credential_source)
32
+
33
+ credential_source ||= default_credential_source
34
+ options = make_creds_options(credential_source, scopes)
35
+ credential_factory.make_creds(options).tap(&:fetch_access_token)
36
+ end
37
+
38
+ private
39
+
40
+ # Reads credential source from `~/.google-api-credential.json`
41
+ #
42
+ # @return [String] the credential as a string
43
+ #
44
+ # @api private
45
+ #
46
+ private_class_method def self.default_credential_source
47
+ File.read(File.expand_path('~/.google-api-credential.json'))
48
+ end
49
+
50
+ # Constructs creds_options needed to create a credential
51
+ #
52
+ # @param [Google::Auth::*, String, #read] credential_source a credential (which
53
+ # is an object whose class is in the Google::Auth module), a String containing
54
+ # the credential, or a IO object with the credential ready to be read.
55
+ #
56
+ # @return [Hash] returns the cred_options
57
+ #
58
+ # @api private
59
+ #
60
+ private_class_method def self.make_creds_options(credential_source, scopes)
61
+ { json_key_io: to_io(credential_source), scope: scopes }
62
+ end
63
+
64
+ # Wraps a credential_source that is a string in a StringIO
65
+ #
66
+ # @param [Google::Auth::*, String, #read] credential_source a credential (which
67
+ # is an object whose class is in the Google::Auth module), a String containing
68
+ # the credential, or a IO object with the credential ready to be read.
69
+ #
70
+ # @return [IO, StringIO] returns a StringIO object is a String was passed in.
71
+ #
72
+ # @api private
73
+ #
74
+ private_class_method def self.to_io(credential_source)
75
+ credential_source.is_a?(String) ? StringIO.new(credential_source) : credential_source
76
+ end
77
+
78
+ # Determines if a credential_source is already a credential
79
+ #
80
+ # @param [Google::Auth::*, String, #read] credential_source a credential (which
81
+ # is an object whose class is in the Google::Auth module), a String containing
82
+ # the credential, or a IO object with the credential ready to be read.
83
+ #
84
+ # @return [Boolean] true if the credential source is an object whose class is in the
85
+ # Google::Auth module.
86
+ #
87
+ # @api private
88
+ #
89
+ private_class_method def self.credential?(credential_source)
90
+ credential_source.class.name.start_with?('Google::Auth::')
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,174 @@
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 = self.class.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
+
132
+ # A hash of schemas keyed by the schema name loaded from the Google Discovery API
133
+ #
134
+ # @example
135
+ # SheetsV4.api_object_schemas #=> { 'PersonSchema' => { 'type' => 'object', ... } ... }
136
+ #
137
+ # @return [Hash<String, Object>] a hash of schemas keyed by schema name
138
+ #
139
+ def self.api_object_schemas
140
+ schema_load_semaphore.synchronize { @api_object_schemas ||= load_api_object_schemas }
141
+ end
142
+
143
+ # Validate
144
+ # A mutex used to synchronize access to the schemas so they are only loaded
145
+ # once.
146
+ #
147
+ @schema_load_semaphore = Thread::Mutex.new
148
+
149
+ class << self
150
+ # A mutex used to synchronize access to the schemas so they are only loaded once
151
+ #
152
+ # @return [Thread::Mutex]
153
+ #
154
+ # @api private
155
+ #
156
+ attr_reader :schema_load_semaphore
157
+ end
158
+
159
+ # Load the schemas from the Google Discovery API
160
+ #
161
+ # @return [Hash<String, Object>] a hash of schemas keyed by schema name
162
+ #
163
+ # @api private
164
+ #
165
+ def self.load_api_object_schemas
166
+ source = 'https://sheets.googleapis.com/$discovery/rest?version=v4'
167
+ resp = Net::HTTP.get_response(URI.parse(source))
168
+ data = resp.body
169
+ JSON.parse(data)['schemas'].tap do |schemas|
170
+ schemas.each { |_name, schema| schema['unevaluatedProperties'] = false }
171
+ end
172
+ end
173
+ end
174
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SheetsV4
4
- VERSION = '0.1.1'
4
+ # The version of this gem
5
+ VERSION = '0.4.0'
5
6
  end
data/lib/sheets_v4.rb CHANGED
@@ -1,8 +1,101 @@
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/credential_creator'
6
+ require_relative 'sheets_v4/validate_api_object'
4
7
 
8
+ require 'google/apis/sheets_v4'
9
+ require 'json'
10
+ require 'logger'
11
+ require 'net/http'
12
+
13
+ # Unofficial helpers for the Google Sheets V4 API
14
+ #
15
+ # @api public
16
+ #
5
17
  module SheetsV4
6
- class Error < StandardError; end
7
- # Your code goes here...
18
+ # Create a new Google::Apis::SheetsV4::SheetsService object
19
+ #
20
+ # Simplifies creating and configuring a the credential.
21
+ #
22
+ # @example using the crednetial in `~/.google-api-credential`
23
+ # SheetsV4.sheets_service
24
+ #
25
+ # @example using a credential passed in as a string
26
+ # credential_source = File.read(File.join(Dir.home, '.credential'))
27
+ # SheetsV4.sheets_service(credential_source:
28
+ #
29
+ # @param credential_source [nil, String, IO, Google::Auth::*] may
30
+ # be either an already constructed credential, the credential read into a String or
31
+ # an open file with the credential ready to be read. Passing `nil` will result
32
+ # in the credential being read from `~/.google-api-credential.json`.
33
+ #
34
+ # @param scopes [Object, Array] one or more scopes to access.
35
+ #
36
+ # @param credential_creator [#credential] Used to inject the credential creator for
37
+ # testing.
38
+ #
39
+ # @return a new SheetsService instance
40
+ #
41
+ def self.sheets_service(credential_source: nil, scopes: nil, credential_creator: SheetsV4::CredentialCreator)
42
+ credential_source ||= File.read(File.expand_path('~/.google-api-credential.json'))
43
+ scopes ||= [Google::Apis::SheetsV4::AUTH_SPREADSHEETS]
44
+
45
+ Google::Apis::SheetsV4::SheetsService.new.tap do |service|
46
+ service.authorization = credential_creator.call(credential_source, scopes)
47
+ end
48
+ end
49
+
50
+ # Validate the object using the named JSON schema
51
+ #
52
+ # The JSON schemas are loaded from the Google Disocvery API. The schemas names are
53
+ # returned by `SheetsV4.api_object_schemas.keys`.
54
+ #
55
+ # @example
56
+ # schema_name = 'BatchUpdateSpreadsheetRequest'
57
+ # object = { 'requests' => [] }
58
+ # SheetsV4.validate_api_object(schema_name:, object:)
59
+ #
60
+ # @param schema_name [String] the name of the schema to validate against
61
+ # @param object [Object] the object to validate
62
+ # @param logger [Logger] the logger to use for logging error, info, and debug message
63
+ #
64
+ # @raise [RuntimeError] if the object does not conform to the schema
65
+ #
66
+ # @return [void]
67
+ #
68
+ def self.validate_api_object(schema_name:, object:, logger: Logger.new(nil))
69
+ ValidateApiObject.new(logger).call(schema_name, object)
70
+ end
71
+
72
+ # Given the name of the color, return a Google Sheets API color object
73
+ #
74
+ # Available color names are listed using `SheetsV4.color_names`.
75
+ #
76
+ # The object returned is frozen. If you want a color you can change (for instance,
77
+ # to adjust the `alpha` attribute), you must clone the object returned.
78
+ #
79
+ # @example
80
+ # SheetsV4::Color.color(:red) #=> { "red": 1.0, "green": 0.0, "blue": 0.0 }
81
+ #
82
+ # @param name [#to_sym] the name of the color requested
83
+ #
84
+ # @return [Hash] The color requested e.g. `{ "red": 1.0, "green": 0.0, "blue": 0.0 }`
85
+ #
86
+ # @api public
87
+ #
88
+ def self.color(name)
89
+ SheetsV4::Color::COLORS[name.to_sym] || raise("Color #{name} not found")
90
+ end
91
+
92
+ # List the names of the colors available to use in the Google Sheets API
93
+ #
94
+ # @example
95
+ # SheetsV4::Color.color_names #=> [:black, :white, :red, :green, :blue, :yellow, :magenta, :cyan, ...]
96
+ #
97
+ # @return [Array<Symbol>] the names of the colors available
98
+ # @api public
99
+ #
100
+ def self.color_names = SheetsV4::Color::COLORS.keys
8
101
  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.1.1
4
+ version: 0.4.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-22 00:00:00.000000000 Z
11
+ date: 2023-09-30 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,12 @@ files:
227
256
  - LICENSE.txt
228
257
  - README.md
229
258
  - Rakefile
259
+ - examples/README.md
260
+ - examples/set_background_color
230
261
  - lib/sheets_v4.rb
262
+ - lib/sheets_v4/color.rb
263
+ - lib/sheets_v4/credential_creator.rb
264
+ - lib/sheets_v4/validate_api_object.rb
231
265
  - lib/sheets_v4/version.rb
232
266
  homepage: https://github.com/main-branch/sheets_v4
233
267
  licenses: