google_docs 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e938ee8f78128013178af8e98317c6e549d020b3
4
+ data.tar.gz: 5c9cb8b317f216569b5fff1186005cbf2367d0ee
5
+ SHA512:
6
+ metadata.gz: e58009e7812d392b788e08a6ca49da6af122286750bd03c54d63bac7c20f1b98bbad281fb1988ca5bf1908974e887fae688843f42d8005e7419bff5668d78257
7
+ data.tar.gz: 4f502985f94c912c7b26de553afd5dfce7436bb18cefbb6792b7f2df4bd5dad601e350333f94335d90d2f289f0cffe0ec577ffa64fe3c0ea544806aa2350482f
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ifuelen@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'google-api-client'
5
+ gem 'pry-nav', group: [:development, :test]
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ google_docs (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.5.0)
10
+ public_suffix (~> 2.0, >= 2.0.2)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ faraday (0.11.0)
14
+ multipart-post (>= 1.2, < 3)
15
+ google-api-client (0.9.28)
16
+ addressable (~> 2.3)
17
+ googleauth (~> 0.5)
18
+ httpclient (~> 2.7)
19
+ hurley (~> 0.1)
20
+ memoist (~> 0.11)
21
+ mime-types (>= 1.6)
22
+ representable (~> 2.3.0)
23
+ retriable (~> 2.0)
24
+ googleauth (0.5.1)
25
+ faraday (~> 0.9)
26
+ jwt (~> 1.4)
27
+ logging (~> 2.0)
28
+ memoist (~> 0.12)
29
+ multi_json (~> 1.11)
30
+ os (~> 0.9)
31
+ signet (~> 0.7)
32
+ httpclient (2.8.3)
33
+ hurley (0.2)
34
+ jwt (1.5.6)
35
+ little-plugger (1.1.4)
36
+ logging (2.1.0)
37
+ little-plugger (~> 1.1)
38
+ multi_json (~> 1.10)
39
+ memoist (0.15.0)
40
+ method_source (0.8.2)
41
+ mime-types (3.1)
42
+ mime-types-data (~> 3.2015)
43
+ mime-types-data (3.2016.0521)
44
+ multi_json (1.12.1)
45
+ multipart-post (2.0.0)
46
+ os (0.9.6)
47
+ pry (0.10.4)
48
+ coderay (~> 1.1.0)
49
+ method_source (~> 0.8.1)
50
+ slop (~> 3.4)
51
+ pry-nav (0.2.4)
52
+ pry (>= 0.9.10, < 0.11.0)
53
+ public_suffix (2.0.5)
54
+ rake (10.5.0)
55
+ representable (2.3.0)
56
+ uber (~> 0.0.7)
57
+ retriable (2.1.0)
58
+ rspec (3.5.0)
59
+ rspec-core (~> 3.5.0)
60
+ rspec-expectations (~> 3.5.0)
61
+ rspec-mocks (~> 3.5.0)
62
+ rspec-core (3.5.4)
63
+ rspec-support (~> 3.5.0)
64
+ rspec-expectations (3.5.0)
65
+ diff-lcs (>= 1.2.0, < 2.0)
66
+ rspec-support (~> 3.5.0)
67
+ rspec-mocks (3.5.0)
68
+ diff-lcs (>= 1.2.0, < 2.0)
69
+ rspec-support (~> 3.5.0)
70
+ rspec-support (3.5.0)
71
+ signet (0.7.3)
72
+ addressable (~> 2.3)
73
+ faraday (~> 0.9)
74
+ jwt (~> 1.5)
75
+ multi_json (~> 1.10)
76
+ slop (3.6.0)
77
+ uber (0.0.15)
78
+
79
+ PLATFORMS
80
+ ruby
81
+
82
+ DEPENDENCIES
83
+ bundler (~> 1.14)
84
+ google-api-client
85
+ google_docs!
86
+ pry-nav
87
+ rake (~> 10.0)
88
+ rspec (~> 3.5)
89
+
90
+ BUNDLED WITH
91
+ 1.16.0.pre.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 JetRuby Agency
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,257 @@
1
+ # Google Docs
2
+
3
+ `google_docs` is a wrapper around [google-api-ruby-client](https://github.com/google/google-api-ruby-client).
4
+ It allows you to get information about the spreadsheet and format it.
5
+ Currently, gem consists of two classes `GoogleDocs::SpreadSheet` and `GoogleDocs::Sheet` and uses `Sheets API V4`.
6
+
7
+ Main features:
8
+ - Set up columns width
9
+ - Update grid properties of the sheet (freeze columns/rows)
10
+ - Update and format cells
11
+ - Merge/unmerge cells in the given range
12
+ - Export google sheet documents as PDF
13
+ - Read information from certain sheet
14
+
15
+ ### Pre requirement
16
+ In order to use our gem your application should have configured [Google OmniAuth](https://github.com/google/google-api-ruby-client#authorization) already. Also you should configure [Google Sheet API](https://developers.google.com/sheets/api/quickstart/ruby)
17
+
18
+ ### Installation
19
+ You can add it to your Gemfile with:
20
+ ```sh
21
+ gem 'google_docs'
22
+ ```
23
+ Then run `bundle install`
24
+
25
+ ### Getting started
26
+ Initialize new object of `GoogleDocs::SpreadSheet` class
27
+ ```sh
28
+ spreadsheet = GoogleDocs::SpreadSheet.new('some_google_access_token', 'some_spreadsheet_id')
29
+ ```
30
+ After successfull intialization we can call the following methods:
31
+ * `spreadsheet.properties` returns properties of the spreadsheet
32
+ * `spreadsheet.sheets` get all sheets of the spreadsheet.
33
+
34
+ If you want to get sheet directly by key:
35
+ ```sh
36
+ sheet = GoogleDocs::Sheet.new(spreadsheet_id: required, sheet_id: required, google_access_token: required)
37
+ ```
38
+ By default, after object initialization there are no modification request to be executed.
39
+ You should prepare requests and only then call `sheet.apply_changes!` to send requests.
40
+ To add requests you can run following methods:
41
+
42
+ * `sheet.setup_columns_width(data)` - build request to set up columns width. Argument `data` should have the following format:
43
+ ```
44
+ {
45
+ 'A' => integer,
46
+ 'D' => integer,
47
+ ...
48
+ }
49
+ ```
50
+ The keys of hash are the columns letters and values of hash - width in pixels for these names in sheet respectively.
51
+ For instance, suppose, you have the following sheet: ![sheet_without_width](/samples/google_sheet_without_width.png)
52
+ and our data are the following:
53
+ ```
54
+ {
55
+ 'A' => 50,
56
+ 'B' => 150,
57
+ 'C' => 150,
58
+ 'D' => 100,
59
+ 'E' => 50,
60
+ 'F' => 100,
61
+ 'G' => 700
62
+ }
63
+ ```
64
+ After our method have been executed we receive:
65
+ ![sheet_with_width](/samples/google_sheet_set_width.png)
66
+
67
+
68
+ * `sheet.update_grid_properties(props)` - build request to update properties of the sheet, i.e. to freeze rows and columns while user is scrolling document. Argument `props` must have the following format:
69
+
70
+ ```
71
+ {
72
+ row_count: integer,
73
+ column_count: integer,
74
+ frozen_row_count: integer,
75
+ frozen_column_count: integer,
76
+ hide_gridlines: boolean
77
+ }
78
+ ```
79
+
80
+ For instance, suppose, you have the following data:
81
+ ```
82
+ props = {
83
+ row_count: 7,
84
+ column_count: 7,
85
+ frozen_row_count: 2,
86
+ frozen_column_count: 1,
87
+ hide_gridlines: true
88
+ }
89
+ ```
90
+ After our method have been executed we receive:
91
+ ![update_grid_properties](/samples/google_sheet_update_grid_properties.png)
92
+
93
+
94
+
95
+ * `sheet.update_cells(data)` - build request to update all cells in a range with new data.
96
+ Argument `data` is an array with rows. Each row is an array of cells. Cell can be a String, Number, Boolean or Hash.
97
+ When the value of cell starts from '=' it will be sent as formula.
98
+ When cell is a Hash it can contain additional formatting options.
99
+ Schema of the `data` attribute:
100
+ ```
101
+ [
102
+ [
103
+ {
104
+ value: 'any cell value'
105
+ borders: {
106
+ left, right, top, bottom: {
107
+ style: :dotted, :dashed, :solid, :double
108
+ width: 1..3
109
+ color: '#RRGGBB'
110
+ }
111
+ }
112
+ background_color: '#RRGGBB'
113
+ horizontal_alignment: :left, :center, :right
114
+ vertical_alignment: :top, :middle, :bottom
115
+ hyperlink_display_type: :linked, :plain_text
116
+ wrap_strategy: :overflow_cell, :legacy_wrap, :clip, :wrap
117
+ text_direction: :left_to_right, :right_to_left
118
+ number_format: {
119
+ type: :text, :number, :percent, :currency, :date, :time, :datetime, :scientific
120
+ pattern: "#,##0.0000"
121
+ }
122
+ text_format: {
123
+ foreground_color: '#RRGGBB'
124
+ font_family: 'Terminus'
125
+ font_size: integer
126
+ bold: boolean
127
+ italic: boolean
128
+ strikethrough: boolean
129
+ underline: boolean
130
+ }
131
+ },
132
+ 'string cell',
133
+ number_cell
134
+ ],
135
+ # new row
136
+ [ {cell 1}, {cell 2}, ... {cell n} ]
137
+ ]
138
+ ```
139
+
140
+ For instance, data could look like:
141
+
142
+ ```
143
+ data = [
144
+ [{ value: 'books', background_color: '#f1c232', text_format: { bold: true }, horizontal_alignment: :left,
145
+ borders: { bottom: { style: :solid, width: 3, color: '#RRGGBB' },
146
+ right: { style: :solid, width: 3, color: '#RRGGBB' } } }
147
+ ],
148
+ [
149
+ { value: 'id', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :right },
150
+ { value: 'author', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center },
151
+ { value: 'title', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center },
152
+ { value: 'genre', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center },
153
+ { value: 'price', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center },
154
+ { value: 'publish_date', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center },
155
+ { value: 'description', background_color: '#b7b7b7', text_format: { bold: true }, horizontal_alignment: :center }
156
+ ],
157
+ [
158
+ { value: 'bk101', text_format: { foreground_color: '#0143DF' }, horizontal_alignment: :right },
159
+ { value: 'Gambardella, Matthew', text_format: { italic: true }, horizontal_alignment: :center },
160
+ { value: "XML Developer's Guide", text_format: { underline: true }, number_format: { type: :text }, horizontal_alignment: :center },
161
+ { value: 'Computer', text_format: { bold: true }, horizontal_alignment: :center },
162
+ { value: 44.95, number_format: { type: :currency }, text_format: { strikethrough: true, foreground_color: '#FF0000' }, horizontal_alignment: :center },
163
+ { value: '10/01/2000', number_format: { type: :date}, horizontal_alignment: :center },
164
+ { value: 'An in-depth look at creating applications', text_format: { font_family: 'Courier New' }, horizontal_alignment: :left },
165
+ ],
166
+ [
167
+ { value: 'bk102', text_format: { foreground_color: '#0143DF' }, horizontal_alignment: :right },
168
+ { value: 'Ralls, Kim', text_format: { italic: true }, horizontal_alignment: :center },
169
+ { value: "Midnight Rain", text_format: { underline: true }, number_format: { type: :text }, horizontal_alignment: :center },
170
+ { value: 'Fantasy', text_format: { bold: true }, horizontal_alignment: :center },
171
+ { value: 10.99, number_format: { type: :currency }, horizontal_alignment: :center },
172
+ { value: '16/12/2000', number_format: { type: :date}, horizontal_alignment: :center },
173
+ { value: 'A former architect battles corporate zombies, an evil sorceress', text_format: { font_family: 'Courier New' }, horizontal_alignment: :left },
174
+ ]
175
+ ]
176
+ ```
177
+ After our method have been executed we receive:
178
+ ![update_cells](/samples/google_sheet_update_cells.png)
179
+
180
+
181
+ * `sheet.merge_cells(props)` - build request to merge cells in the given range. Index count starts from 0. Argument `props` has the following format:
182
+
183
+ ```
184
+ {
185
+ type: :merge_all, :merge_columns, :merge_rows
186
+ range: {
187
+ start_row_index: integer,
188
+ end_row_index: integer,
189
+ start_column_index: integer,
190
+ end_column_index: integer
191
+ }
192
+ }
193
+ ```
194
+ For instance,
195
+ ```
196
+ props = {
197
+ type: :merge_rows,
198
+ range: {
199
+ start_row_index: 0,
200
+ end_row_index: 1,
201
+ start_column_index: 0,
202
+ end_column_index: 7
203
+ }
204
+ }
205
+ ```
206
+ After our method have been executed we receive:
207
+ ![merge_cells](/samples/google_sheet_merge_cells.png)
208
+
209
+ * `sheet.unmerge_cells(range)` - build request to unmerge cells in the given range, where argument `range` has the following format:
210
+
211
+ ```
212
+ {
213
+ start_row_index: integer,
214
+ end_row_index: integer,
215
+ start_column_index: integer,
216
+ end_column_index: integer
217
+ }
218
+ ```
219
+ * `sheet.apply_changes!` - execute all reguests.
220
+
221
+
222
+ * `sheet.get_rows_values` - read information from each row in the sheet. Output example:
223
+ ```
224
+ [
225
+ ["books"],
226
+ ["id", "author", "title", "genre", "price", "publish_date", "description"],
227
+ ["bk101", "Gambardella, Matthew", "XML Developer's Guide", "Computer", "44.95", "2000-10-01", "An in-depth look at creating applications"],
228
+ ["bk102", "Ralls, Kim", "Midnight Rain", "Fantasy", "5.95", "2000-12-16", "A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world"],
229
+ ["bk103", "Corets, Eva", "Maeve Ascendant", "Fantasy", "5.95", "2000-11-17", "After the collapse of a nanotechnology society in England, the young survivors lay the foundation for a new society"],
230
+ ["bk104", "Thurman, Paula", "Splish Splash", "Romance", "4.95", "2000-11-02", "A deep sea diver finds true love twenty thousand leagues beneath the sea"],
231
+ ["bk105", "Kress, Peter", "Paradox Lost", "Science Fiction", "6.95", "2000-11-02", "After an inadvertant trip through a Heisenberg Uncertainty Device, James Salway discovers the problems of being quantum"]
232
+ ]
233
+ ```
234
+
235
+ * `sheet.download_pdf { |file| model.update(attachment: file) }` - export google sheet document as PDF
236
+
237
+ ## Contributing
238
+
239
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jetruby/google_docs-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
240
+
241
+ ## License
242
+
243
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
244
+
245
+ ## Code of Conduct
246
+
247
+ Everyone interacting in the ApolloUploadServer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jetruby/google_docs-ruby/blob/master/CODE_OF_CONDUCT.md).
248
+
249
+ ## About JetRuby
250
+ ApolloUploadServer is maintained and founded by JetRuby Agency.
251
+
252
+ We love open source software!
253
+ See [our projects][portfolio] or
254
+ [contact us][contact] to design, develop, and grow your product.
255
+
256
+ [portfolio]: http://jetruby.com/portfolio/
257
+ [contact]: http://jetruby.com/#contactUs
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+ task default: :spec
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'google_docs'
5
+ require 'irb'
6
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../lib/google_docs/version', __FILE__)
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'google_docs'
5
+ spec.version = GoogleDocs::VERSION
6
+ spec.authors = ['JetRuby']
7
+ spec.email = ['engineering@jetruby.com']
8
+
9
+ spec.summary = 'Google Docs'
10
+ spec.description = 'A simple gem to create styles for google document'
11
+ spec.homepage = 'http://jetruby.com'
12
+ spec.license = 'MIT'
13
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
14
+ f.match(%r{^(test|spec|features)/})
15
+ end
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.14'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'rspec', '~> 3.5'
23
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'google_docs/version'
2
+ require 'google/apis/sheets_v4'
3
+ require_relative 'google_docs/sheet'
4
+ require_relative 'google_docs/spread_sheet'
5
+
6
+ module GoogleDocs
7
+ SERVICE = Google::Apis::SheetsV4
8
+ end
@@ -0,0 +1,277 @@
1
+ require 'tempfile'
2
+ require_relative '../string_utils'
3
+
4
+ module GoogleDocs
5
+ class Sheet
6
+ LETTER_LIST = ('A'..'ZZZ').to_a.freeze
7
+ # Set class instance variables
8
+ # @param spreadsheet_id [String] id of spreadsheet
9
+ # @param sheet_id [String] id of sheet
10
+ # @param google_access_token [String]
11
+ # @param service [Google::Apis::SheetsV4::SheetsService]
12
+ # param google_access_token [String] google access token
13
+ # @note if you have `service` you don't need `google_access_token`
14
+ def initialize(spreadsheet_id:, sheet_id:, google_access_token: nil, service: nil)
15
+ if service.nil?
16
+ @service = SERVICE::SheetsService.new
17
+ @service.authorization = google_access_token
18
+ else
19
+ @service = service
20
+ end
21
+
22
+ @spreadsheet = @service.get_spreadsheet(spreadsheet_id)
23
+ @sheet = @spreadsheet.sheets.find { |sheet| sheet.properties.sheet_id == sheet_id.to_i }
24
+ @requests = []
25
+
26
+ raise ArgumentError, 'Invalid sheet id' if @sheet.nil?
27
+ end
28
+
29
+ # return all information from each row in sheet
30
+ def get_rows_values
31
+ last_column_letter = LETTER_LIST[@sheet.properties.grid_properties.column_count - 1]
32
+ last_row_number = @sheet.properties.grid_properties.row_count
33
+ @service.get_spreadsheet_values(
34
+ @spreadsheet.spreadsheet_id, "#{@sheet.properties.title}!A1:#{last_column_letter}#{last_row_number}"
35
+ ).values
36
+ end
37
+
38
+ # create pdf file of sheet's content
39
+ # @yield [file] Gives file to the block
40
+ def download_pdf
41
+ file = Tempfile.new(['sheet', '.pdf'])
42
+ file.binmode
43
+ file.write(pdf_request)
44
+ yield(file)
45
+ file.close
46
+ end
47
+
48
+ def pdf_request
49
+ @service.http(:get, "https://docs.google.com/spreadsheets/d/#{@spreadsheet.spreadsheet_id}/export?exportFormat=pdf&gid=#{@sheet.properties.sheet_id}")
50
+ end
51
+
52
+ # @param data [Hash] keys are the columns names and values indicate desirable columns width in sheet
53
+ # {
54
+ # {
55
+ # 'A' => 'integer value',
56
+ # 'D' => 'integer value',
57
+ # ...
58
+ # }
59
+ # }
60
+ # @note 'integer value' is positive pixel size for Google::Apis::SheetsV4::DimensionProperties
61
+ def setup_columns_width(data)
62
+ data.each do |key, value|
63
+ append_request(
64
+ update_dimension_properties: SERVICE::UpdateDimensionPropertiesRequest.new(
65
+ range: SERVICE::DimensionRange.new(
66
+ sheet_id: @sheet.properties.sheet_id,
67
+ dimension: 'COLUMNS',
68
+ start_index: LETTER_LIST.index(key),
69
+ end_index: LETTER_LIST.index(key) + 1
70
+ ),
71
+ properties: SERVICE::DimensionProperties.new(pixel_size: value),
72
+ fields: 'pixelSize'
73
+ )
74
+ )
75
+ end
76
+ true
77
+ end
78
+
79
+ # @param props [Hash]
80
+ # @example of props value
81
+ # https://developers.google.com/sheets/reference/rest/v4/spreadsheets#GridProperties
82
+ # {
83
+ # row_count: integer
84
+ # column_count: integer
85
+ # frozen_row_count: integer
86
+ # frozen_column_count: integer
87
+ # hide_gridlines: bool
88
+ # }
89
+ def update_grid_properties(props)
90
+ append_request(
91
+ update_sheet_properties: SERVICE::UpdateSheetPropertiesRequest.new(
92
+ fields: props.keys.map { |field| "gridProperties.#{StringUtils.camelize(field.to_s)}" }.join(','),
93
+ properties: SERVICE::SheetProperties.new(
94
+ grid_properties: SERVICE::GridProperties.new(props),
95
+ sheet_id: @sheet.properties.sheet_id
96
+ )
97
+ )
98
+ )
99
+ end
100
+
101
+ # @param data [Array<Array>]
102
+ # @example of data value
103
+ # [
104
+ # [ 'text', 'text', ...], # first row
105
+ # [], # second row (empty)
106
+ # [ 'text', { value: 'text', ...props }] # third row
107
+ # ...
108
+ # ]
109
+ def update_cells(data)
110
+ append_request(
111
+ update_cells: SERVICE::UpdateCellsRequest.new(
112
+ rows: format_rows_from(data),
113
+ fields: '*',
114
+ range: grid_range
115
+ )
116
+ )
117
+ end
118
+
119
+ # @param props [Hash]
120
+ # @example of props value
121
+ # {
122
+ # type: :merge_all, :merge_columns, :merge_rows
123
+ # range: {
124
+ # start_row_index: integer
125
+ # end_row_index: integer
126
+ # start_column_index: integer
127
+ # end_column_index: integer
128
+ # }
129
+ # }
130
+ def merge_cells(props)
131
+ append_request(
132
+ merge_cells: SERVICE::MergeCellsRequest.new(
133
+ merge_type: props[:type],
134
+ range: grid_range(props[:range])
135
+ )
136
+ )
137
+ end
138
+
139
+ # @param range [Hash]
140
+ # @example of props value
141
+ # {
142
+ # start_row_index: integer
143
+ # end_row_index: integer
144
+ # start_column_index: integer
145
+ # end_column_index: integer
146
+ # }
147
+ def unmerge_cells(range)
148
+ append_request(
149
+ unmerge_cells: SERVICE::UnmergeCellsRequest.new(
150
+ range: grid_range(range)
151
+ )
152
+ )
153
+ end
154
+
155
+ def apply_changes!
156
+ @service.batch_update_spreadsheet(
157
+ @spreadsheet.spreadsheet_id, SERVICE::BatchUpdateSpreadsheetRequest.new(requests: @requests)
158
+ )
159
+ @requests = []
160
+ end
161
+
162
+ private
163
+
164
+ def append_request(request_params)
165
+ @requests << SERVICE::Request.new(request_params)
166
+ end
167
+
168
+ def grid_range(range = {})
169
+ SERVICE::GridRange.new(range.merge(sheet_id: @sheet.properties.sheet_id))
170
+ end
171
+
172
+ def format_rows_from(values)
173
+ values.map do |row_cells|
174
+ row_data = row_cells.map do |cell|
175
+ if cell.is_a? Hash
176
+ SERVICE::CellData.new(
177
+ user_entered_value: extended_value_for(cell[:value]),
178
+ user_entered_format: format_cell_with(cell.except(:value))
179
+ )
180
+ else
181
+ SERVICE::CellData.new(user_entered_value: extended_value_for(cell))
182
+ end
183
+ end
184
+ SERVICE::RowData.new(values: row_data)
185
+ end
186
+ end
187
+
188
+ def detect_type_for(value)
189
+ case value
190
+ when String
191
+ value.start_with?('=') ? :formula_value : :string_value
192
+ when Integer, Float
193
+ :number_value
194
+ when TrueClass, FalseClass
195
+ :bool_value
196
+ else
197
+ :string_value
198
+ end
199
+ end
200
+
201
+ def extended_value_for(value)
202
+ SERVICE::ExtendedValue.new(detect_type_for(value || value.to_s) => (value || value.to_s))
203
+ end
204
+
205
+ #
206
+ # {
207
+ # borders: {
208
+ # left, right, top, bottom: {
209
+ # style: :dotted, :dashed, :solid, :double
210
+ # width: 1..3
211
+ # color: '#RRGGBB'
212
+ # }
213
+ # }
214
+ # background_color: '#RRGGBB'
215
+ # horizontal_alignment: :left, :center, :right
216
+ # vertical_alignment: :top, :middle, :bottom
217
+ # hyperlink_display_type: :linked, :plain_text
218
+ # wrap_strategy: :overflow_cell, :legacy_wrap, :clip, :wrap
219
+ # text_direction: :left_to_right, :right_to_left
220
+ # number_format: {
221
+ # type: :text, :number, :percent, :currency, :date, :time, :datetime, :scientific
222
+ # pattern: "#,##0.0000"
223
+ # }
224
+ # text_format: {
225
+ # foreground_color: '#RRGGBB'
226
+ # font_family: 'Terminus'
227
+ # font_size: integer
228
+ # bold: bool
229
+ # italic: bool
230
+ # strikethrough: bool
231
+ # underline: bool
232
+ # }
233
+ # }
234
+
235
+ def format_cell_with(params)
236
+ params = params.dup
237
+ params[:background_color] &&= format_color params[:background_color]
238
+ params[:borders] &&= format_borders params[:borders]
239
+ params[:text_format] &&= format_text params[:text_format]
240
+ params[:number_format] &&= format_number params[:number_format]
241
+ SERVICE::CellFormat.new(params)
242
+ end
243
+
244
+ def format_text(props)
245
+ props = props.dup
246
+ props[:foreground_color] &&= format_color(props[:foreground_color])
247
+ SERVICE::TextFormat.new(props)
248
+ end
249
+
250
+ def format_number(props)
251
+ SERVICE::NumberFormat.new(props)
252
+ end
253
+
254
+ def format_color(hex)
255
+ red, green, blue = hex_to_rgb(hex)
256
+ SERVICE::Color.new(red: red, blue: blue, green: green, alpha: 1.0)
257
+ end
258
+
259
+ def format_borders(borders)
260
+ SERVICE::Borders.new(borders.map { |position, props| [position, format_border(props)] }.to_h)
261
+ end
262
+
263
+ def format_border(props)
264
+ SERVICE::Border.new(
265
+ style: props[:style],
266
+ width: props[:width],
267
+ color: format_color(props[:color])
268
+ )
269
+ end
270
+
271
+ # '#RRGGBB' => [0..1, 0..1, 0..1]
272
+
273
+ def hex_to_rgb(hex)
274
+ hex.scan(/[^#]{2}/).map { |color| (color.to_i(16).to_f / 255) }
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,19 @@
1
+ module GoogleDocs
2
+ class SpreadSheet
3
+ def initialize(google_access_token, spreadsheet_id)
4
+ @service = SERVICE::SheetsService.new
5
+ @service.authorization = google_access_token
6
+ @spreadsheet = @service.get_spreadsheet(spreadsheet_id)
7
+ end
8
+
9
+ def sheets
10
+ @spreadsheet.sheets.map do |sheet|
11
+ Sheet.new(spreadsheet_id: @spreadsheet.spreadsheet_id, sheet_id: sheet.properties.sheet_id, service: @service)
12
+ end
13
+ end
14
+
15
+ def properties
16
+ @spreadsheet.properties
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module GoogleDocs
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,10 @@
1
+ module StringUtils
2
+ def self.camelize(string, uppercase_first_letter = true)
3
+ string = if uppercase_first_letter
4
+ string.sub(/^[a-z\d]*/) { $&.capitalize }
5
+ else
6
+ string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
7
+ end
8
+ string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }.gsub('/', '::')
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_docs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - JetRuby
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ description: A simple gem to create styles for google document
56
+ email:
57
+ - engineering@jetruby.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CODE_OF_CONDUCT.md
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - google_docs.gemspec
71
+ - lib/google_docs.rb
72
+ - lib/google_docs/sheet.rb
73
+ - lib/google_docs/spread_sheet.rb
74
+ - lib/google_docs/version.rb
75
+ - lib/string_utils.rb
76
+ - samples/google_sheet_merge_cells.png
77
+ - samples/google_sheet_set_width.png
78
+ - samples/google_sheet_update_cells.png
79
+ - samples/google_sheet_update_grid_properties.png
80
+ - samples/google_sheet_without_width.png
81
+ homepage: http://jetruby.com
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.6.11
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Google Docs
105
+ test_files: []