spreadsheet_goodies 0.0.4 → 0.0.5

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
  SHA1:
3
- metadata.gz: 0ac665483c2a857085d25a1237cbab883b2b12c3
4
- data.tar.gz: 01d5aeed49efd551611bfc80981258a9d4346dcf
3
+ metadata.gz: 1cb1da443d319f91e093023601d1688081db8344
4
+ data.tar.gz: 10d6c084df8ae3da6ba1a1322805750d52191a3d
5
5
  SHA512:
6
- metadata.gz: f963fcdda1e06d423fdb49584e9d19e7b8507eb2a20bbebd0ca4716b01b6aef9134594819aede023b1dfd044cbb93e48209ef1e173f8a52e5b5bd0e281d0d0f7
7
- data.tar.gz: 2c03a6bb43be7e8794ea8d03e8f7c23b4a226b16b6d777d1aff7309526a2467d077d7a2bee5e5463fdd8dea78750940d1948125dc2e91311d0113fe3d244ad37
6
+ metadata.gz: ec886820ca483030f181f1d82542660aa9f54a898f9d0c97478648937c7e4ea9f44cf5086a508efe4442acc4ed6f002b941e8c4f3680912fe77ee031f95a1078
7
+ data.tar.gz: 434ed1a593759e605e34f18f345245a78ea7c208c64f9f9a1f9dbdd3d1f719f093b6d638a3dfb15e47b206aaecfb3965da89e0ad37bdf8f969b7fa1529267fe9
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # SpreadsheetGoodies
2
2
 
3
- SpreadsheetGoodies is a collection of tools to help work with spreadsheets in
4
- Excel and Google Spreadhseets formats. It relies heavily on other gems to make
5
- the actual work of reading and writing to spreadsheet documents. It main
3
+ SpreadsheetGoodies is a collection of tools to help work with spreadsheets in
4
+ Excel and Google Spreadhseets formats. It relies heavily on other gems to make
5
+ the actual work of reading and writing to spreadsheet documents. It main
6
6
  features are:
7
7
 
8
- * Read a spreadseet as an array of arrays to allow aceessing its data without
8
+ * Read a spreadseet as an array of arrays to allow aceessing its data without
9
9
  using the original document
10
10
  * Access a row's elements using the column titles as keys"
11
11
 
@@ -28,24 +28,75 @@ Or install it yourself as:
28
28
  ## Usage
29
29
 
30
30
  Read a Google Spreadsheet:
31
- ```
32
- spreadsheet_key = '1UC43X6aZwlWPCnn...', '~> 0'#, '~> 1.13.2'
33
- sheet = SpreadsheetGoodies::GoogleDriveWorksheet.new(spreadsheet_key, 'Relação Lojas').read_from_google_drive
31
+ ```ruby
32
+ sheet = SpreadsheetGoodies::GoogleDrive.read_worksheet(
33
+ spreadsheet_key: '1UC43X6aZwlWPCnn...', # required,
34
+ worksheet_title: 'sheet1', # optional, first worksheet is loaded if not specified
35
+ )
34
36
  ```
35
37
 
36
38
  Read an Excel workbook:
37
- ```
38
- sheet = SpreadsheetGoodies::ExcelWorksheet.new('~/workbook.xlsx')
39
+ ```ruby
40
+ sheet = SpreadsheetGoodies::Excel.read_worksheet(
41
+ file_pathname: '~/workbook.xlsx', # required,
42
+ worksheet_title_or_index: 'sheet1', # optional, first worksheet is loaded if not specified
43
+ )
39
44
  ```
40
45
 
41
- Iterate over every data row (i.e., all but the header row) and print the value
42
- of a column titled 'Total':
43
- ```
46
+ Iterate over every data row (i.e., all but the header row) and print the value
47
+ of a column titled 'Total':
48
+ ```ruby
44
49
  sheet.data_rows.each do |row|
45
50
  puts "#{row.row_number} -- #{row['Total']}"
46
51
  end
47
52
  ```
48
53
 
54
+ Writing values to cells (only available for GoogleDrive adapter right now):
55
+ ```ruby
56
+ row = sheet[0]
57
+ row[0] = 'First cell'
58
+ row[1] = 'Second cell'
59
+ sheet.commit_writes! # changes are applied to real spreadsheet
60
+ ```
61
+
62
+ ## Logging in to Google Drive
63
+ If you need to access a spreadsheet on Google Drive that is not publicly accessible,
64
+ you are required to setup an authentication method. Currently, there are two available
65
+ authentication methods.
66
+
67
+ ### OAuth2
68
+ To setup OAuth2, first you must configure your Google client id and a client secret
69
+ like the example below. If you don't have a client id yet, you must create a project
70
+ and enable the GoogleDrive API at https://console.developers.google.com. Then you
71
+ need to create a OAuth client id.
72
+ ```ruby
73
+ SpreadsheetGoodies.configure do |config|
74
+ config.login_method = :oauth2
75
+ config.google_client_id = '1012345678904-fdks82jfhe8ojdks7285fj4pnqiejrnbt.apps.googleusercontent.com' # put your real client id here
76
+ config.client_secret = 'Aa-Ku8C-askjfAYKkdjf9ssnf' # put your real secret here
77
+ end
78
+ ```
79
+
80
+ Then run your code. You will be prompted to make the authorization process to obtain
81
+ a refresh token:
82
+ ```
83
+ 1. Open this page:
84
+ https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=1012345678904-fdks82jfhe8ojdks7285fj4pnqiejrnbt.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/drive%20https://spreadsheets.google.com/feeds/
85
+
86
+ 2. Enter the authorization code shown in the page: 4/LADQHhpk7x27BMeP2tIEe_pKuTJmJmZhWoRcBhBmFTVRqSEtcap7Z6s
87
+
88
+ Congratulations! Your refresh token is: 1/c9JDKAUF83_4SPqNc8ldQWe9TdXOxqXvMJJPtmDA2k
89
+ Set the refresh_token in your SpreadsheetGoodies configuration and run your code again
90
+ ```
91
+
92
+ ### Service Accounts
93
+ ```ruby
94
+ SpreadsheetGoodies.configure do |config|
95
+ config.login_method = :service_account
96
+ config.service_account_key_json = '...'
97
+ end
98
+ ```
99
+
49
100
  ## Contributing
50
101
 
51
102
  Bug reports and pull requests are welcome on GitHub at https://github.com/ricardo-jasinski/spreadsheet_goodies.
@@ -54,4 +105,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/ricard
54
105
  ## License
55
106
 
56
107
  The gem is available as open source under the terms of the [Unlicense](http://unlicense.org/UNLICENSE).
57
-
@@ -0,0 +1,39 @@
1
+
2
+ # Base class for all worksheets
3
+ module SpreadsheetGoodies
4
+ class AbstractBaseWorksheet
5
+ attr_reader :rows, :header_row
6
+
7
+ def [](index)
8
+ @rows[index]
9
+ end
10
+
11
+ # Return only the rows that contain data (excludes the header rows)
12
+ def data_rows
13
+ @rows[@num_header_rows..-1]
14
+ end
15
+
16
+ # Finds and returns the first row that contains cell_value at the given column
17
+ def find_row_by_column_value(column_title, cell_value)
18
+ data_rows.each do |row|
19
+ return row if row[column_title] == cell_value
20
+ end
21
+
22
+ nil
23
+ end
24
+
25
+ # Writes to a given cell identified by row and column indexes (they start at 1)
26
+ # This method should be overriden by worksheets that wish to implement writes.
27
+ # Usually this method is not called directly by the gem user, but rather from
28
+ # the modified row itself
29
+ def write_to_cell(row_index, col_index, value)
30
+ raise 'writes are not implemented for this type of worksheet'
31
+ end
32
+
33
+ # Some adapters (like GoogleDrive) require writes to be committed to actually
34
+ # make the changes on the spreadsheet
35
+ def commit_writes!
36
+ raise 'this kind of worksheet does not require writes to be committed'
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,80 @@
1
+ require 'axlsx'
2
+
3
+ # A set of methods to help create and format Excel workbooks
4
+ module SpreadsheetGoodies::Excel
5
+ class WorkbookBuilder
6
+ attr_reader :current_sheet
7
+
8
+ def initialize(output_file_pathname)
9
+ @output_file_pathname = output_file_pathname
10
+ @axlsx_package = Axlsx::Package.new
11
+ end
12
+
13
+ # The underlying Axlsx::Workbook object
14
+ def workbook
15
+ @axlsx_package.workbook
16
+ end
17
+
18
+ def add_worksheet(sheet_name)
19
+ @current_sheet = @workbook.add_worksheet(name: sheet_name)
20
+ setup_current_sheet_styles
21
+ freeze_top_row
22
+ @current_sheet
23
+ end
24
+
25
+ # Add a row to the current sheet using the data row style
26
+ def add_data_row(row_values)
27
+ @current_sheet.add_row(row_values, style: @data_row_style)
28
+ end
29
+
30
+ # Add a row to the current sheet using the header row style
31
+ def add_header_row(row_values)
32
+ @current_sheet.add_row(row_values, style: @header_row_style)
33
+ end
34
+
35
+ # Add filter and sorting controls to a sheet.
36
+ def setup_auto_filter(sheet=nil)
37
+ worksheet = sheet || @current_sheet
38
+ top_left_cell_label = worksheet.dimension.first_cell_reference
39
+ bottom_right_cell_label = worksheet.dimension.last_cell_reference
40
+ filter_range = "#{top_left_cell_label}:#{bottom_right_cell_label}"
41
+ worksheet.auto_filter = filter_range
42
+ end
43
+
44
+ def write_to_file
45
+ @axlsx_package.serialize(@output_file_pathname)
46
+ end
47
+
48
+ def freeze_top_row
49
+ @current_sheet.sheet_view.pane do |pane|
50
+ pane.top_left_cell = 'A2'
51
+ pane.state = :frozen_split
52
+ pane.y_split = 1
53
+ pane.active_pane = :bottom_right
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def setup_current_sheet_styles
60
+ @header_row_style = @current_sheet.styles.add_style(
61
+ alignment: {horizontal: :center, vertical: :center},
62
+ font_name: 'Calibri',
63
+ bg_color: 'FFDDDDDD',
64
+ b: true,
65
+ )
66
+
67
+ @header_row_sodexo_style = @current_sheet.styles.add_style(
68
+ alignment: {horizontal: :center, vertical: :center},
69
+ font_name: 'Calibri',
70
+ bg_color: '002060',
71
+ fg_color: 'FFFFFF',
72
+ b: true,
73
+ )
74
+
75
+ @data_row_style = @current_sheet.styles.add_style(
76
+ font_name: 'Calibri',
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,53 @@
1
+ require_relative '../abstract_base_worksheet'
2
+ require_relative '../row'
3
+
4
+ # A cached copy of an Excel worksheet (a single workbook "tab"), stored as an
5
+ # array of arrays. Individual cells can be accessed by column title, e.g.:
6
+ # worksheet[0]['A column title']
7
+ module SpreadsheetGoodies::Excel
8
+ class Worksheet < SpreadsheetGoodies::AbstractBaseWorksheet
9
+ attr_reader :workbook, :worksheet
10
+
11
+ # @param workbook_file_pathname [String] Full path and filename to Excel workbook document
12
+ # @param worksheet_title_or_index [String|Integer] Sheet name or index (zero-based)
13
+ # within the workbook. Optional; if unspecified, the first sheet will be used.
14
+ # @param num_header_rows [Integer] Number of rows at the top of the sheet that
15
+ # contain headers or stuff other than data. Optional; if unspecified, assumes
16
+ # that a single top row contains the header and all rows below are data.
17
+ def initialize(workbook_file_pathname, worksheet_title_or_index=0, num_header_rows=1)
18
+ @worksheet_title = worksheet_title_or_index
19
+ @num_header_rows = num_header_rows
20
+
21
+ @workbook = case workbook_file_pathname.to_s
22
+ when /\.xls[^x]/ then Roo::Excel.new(workbook_file_pathname, file_warning: :ignore)
23
+ when /\.xlsx/ then Roo::Excelx.new(workbook_file_pathname, file_warning: :ignore)
24
+ end
25
+
26
+ @worksheet = @workbook.sheet(worksheet_title_or_index)
27
+
28
+ @header_row = @worksheet.row(num_header_rows)
29
+
30
+ # Reads all the worksheet rows, one at a time, using Workshete#row (note: reading
31
+ # them all at once using Worksheet#parse didn't work because the first row
32
+ # was missed some times.)
33
+ rows = (1..@worksheet.last_row).map {|row_number| @worksheet.row(row_number) }
34
+
35
+ # Create the rows cache, made of instances of ExcelWorksheetRow
36
+ @rows = rows.collect.with_index do |row, index|
37
+ SpreadsheetGoodies::Row.new(@header_row, index+1, self, *row)
38
+ end
39
+
40
+ self
41
+ end
42
+
43
+ def add_row(row_data)
44
+ last_row_number = @rows.count
45
+ @rows << SpreadsheetGoodies::Row.new(@header_row, last_row_number+1, self, *row_data)
46
+ end
47
+
48
+ def spreadsheet
49
+ @workbook
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module SpreadsheetGoodies::Excel
3
+
4
+ def self.read_worksheet(file_pathname:, worksheet_title_or_index:0, num_header_rows:1)
5
+ Worksheet.new(file_pathname, worksheet_title_or_index, num_header_rows)
6
+ end
7
+
8
+ end
9
+
10
+ # loads all files in excel folder
11
+ Dir[File.join(File.dirname(__FILE__), "excel/**/*.rb")].each { |f| require f }
@@ -0,0 +1,104 @@
1
+ require 'google_drive'
2
+ require 'googleauth'
3
+
4
+ module SpreadsheetGoodies::GoogleDrive
5
+ class Connector
6
+ attr_reader :logger, :session
7
+
8
+ def initialize
9
+ @logger = Logger.new(STDOUT)
10
+ @session = log_into_google_drive
11
+ raise 'Session not found!' unless @session
12
+ end
13
+
14
+ # Reads a GoogleDrive::Spreadsheet object from Google Drive, using the
15
+ # google_drive gem. Documentation for this class can be found here:
16
+ # https://www.rubydoc.info/github/gimite/google-drive-ruby/GoogleDrive/Spreadsheet
17
+ def read_worksheet(spreadsheet_key, worksheet_title=nil)
18
+ puts "Reading sheet '#{worksheet_title}' from Google Drive..."
19
+ spreadsheet = @session.spreadsheet_by_key(spreadsheet_key)
20
+
21
+ if worksheet_title
22
+ worksheet = spreadsheet.worksheet_by_title(worksheet_title)
23
+ if worksheet.nil?
24
+ raise "Worksheet named '#{worksheet_title}' not found in spreadsheet."
25
+ end
26
+ else
27
+ worksheet = spreadsheet.worksheets.first
28
+ end
29
+
30
+ worksheet
31
+ end
32
+
33
+ def read_worksheet_as_string(spreadsheet_key, worksheet_title)
34
+ worksheet = read_worksheet(spreadsheet_key, worksheet_title)
35
+ contents = worksheet.export_as_string.force_encoding('utf-8')
36
+ puts 'Spreadsheet read successfully.'
37
+ contents
38
+ end
39
+
40
+ private
41
+
42
+ def log_into_google_drive
43
+ case SpreadsheetGoodies.configuration.login_method
44
+ when :service_account then log_into_google_drive_via_service_account
45
+ when :oauth2 then log_into_google_drive_via_oauth2
46
+ end
47
+ end
48
+
49
+ # Authenticate with Google Drive via OAuth2.
50
+ # Your OAuth2 ids can be accessed at https://console.developers.google.com/apis/credentials,
51
+ # logged with your Google account in your custom project. The keys are at
52
+ # 'API manager' > 'Credentials'. CLIENT_ID and CLIENT_SECRET are available
53
+ # from the credentials page. The REFRESH_TOKEN must be obtained uncommenting
54
+ # some rows from the method below and obtaining an access_token via a browser
55
+ # logged in to your google account.
56
+ def log_into_google_drive_via_oauth2
57
+ credentials = Google::Auth::UserRefreshCredentials.new(
58
+ client_id: SpreadsheetGoodies.configuration.google_client_id,
59
+ client_secret: SpreadsheetGoodies.configuration.client_secret,
60
+ scope: [
61
+ 'https://www.googleapis.com/auth/drive',
62
+ 'https://spreadsheets.google.com/feeds/',
63
+ ],
64
+ redirect_uri: 'urn:ietf:wg:oauth:2.0:oob'
65
+ )
66
+
67
+ # if has refresh_token, logs in
68
+ if SpreadsheetGoodies.configuration.refresh_token
69
+ credentials.refresh_token = SpreadsheetGoodies.configuration.refresh_token
70
+
71
+ # if refresh_token is missing, prompts user to obtain it
72
+ else
73
+ print("1. Open this page:\n%s\n\n" % credentials.authorization_uri)
74
+ print("2. Enter the authorization code shown in the page: ")
75
+ credentials.code = $stdin.gets.chomp
76
+ credentials.fetch_access_token!
77
+ print("\nCongratulations! Your refresh token is: #{credentials.refresh_token}\n")
78
+ print("Set the refresh_token in your SpreadsheetGoodies configuration and run your code again\n")
79
+ exit(0)
80
+ end
81
+
82
+ begin
83
+ credentials.fetch_access_token!
84
+ rescue Faraday::ConnectionFailed
85
+ logger.info 'Error logging into Google Drive. Is your internet connection down?'
86
+ exit
87
+ end
88
+
89
+ session = GoogleDrive::Session.from_credentials(credentials)
90
+ end
91
+
92
+ # Authenticate with Google Drive via a service account.
93
+ # Your service accounts can be accessed at https://console.developers.google.com/apis/credentials,
94
+ # logged with your Google account in your custom project.
95
+ # The service accounts are at 'IAM and administrator' > 'Service accounts'.
96
+ # The keys are at 'API manager' > 'Credentials'.
97
+ def log_into_google_drive_via_service_account
98
+ session = GoogleDrive::Session.from_service_account_key(
99
+ StringIO.new(SpreadsheetGoodies.configuration.service_account_key_json)
100
+ )
101
+ session
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,68 @@
1
+ require 'csv'
2
+ require_relative '../abstract_base_worksheet'
3
+ require_relative '../row'
4
+
5
+ # A cached copy of a Google Spreadsheets worksheet (i.e., a single workbook "tab"),
6
+ # Individual cells of a row can be accessed by column title.
7
+ # Example:
8
+ # worksheet[0]['A column title']
9
+ module SpreadsheetGoodies::GoogleDrive
10
+ class Worksheet < SpreadsheetGoodies::AbstractBaseWorksheet
11
+ # @param spreadsheet_key [String] Spreadsheet identifier, which can be retrieved
12
+ # from the spreasheet's url
13
+ # @param worksheet_title_or_index [String] Sheet name; if unspecified, the
14
+ # first sheet will be used.
15
+ # @param num_header_rows [Integer] Number of rows at the top of the sheet that
16
+ # contain headers or stuff other than data. Optional; if unspecified, assumes
17
+ # that a single top row contains the header and all rows below are data.
18
+ def initialize(spreadsheet_key, worksheet_title=nil, num_header_rows=1)
19
+ @spreadsheet_key = spreadsheet_key
20
+ @worksheet_title = worksheet_title
21
+ @num_header_rows = num_header_rows
22
+
23
+ read_from_google_drive
24
+ end
25
+
26
+ # Writes to a given cell identified by row and column indexes (they start at 1)
27
+ # @override
28
+ def write_to_cell(row_index, col_index, value)
29
+ underlying_adapter_worksheet[row_index, col_index] = value
30
+ end
31
+
32
+ # Commit writes so they are propagated to the real spreadsheet
33
+ # @override
34
+ def commit_writes!
35
+ underlying_adapter_worksheet.save
36
+ end
37
+
38
+ private
39
+
40
+ # Reads sheet contents and caches it into instance variables to so that it
41
+ # can be accessed later without accessing Google Drive.
42
+ def read_from_google_drive
43
+ worksheet_contents = underlying_adapter_worksheet.export_as_string.force_encoding('utf-8')
44
+
45
+ rows = CSV::parse(worksheet_contents)
46
+
47
+ if SpreadsheetGoodies.configuration.strip_values_on_read
48
+ rows = rows.map do |row|
49
+ row.map {|element| element.try(:strip) }
50
+ end
51
+ end
52
+
53
+ @header_row = rows[@num_header_rows-1]
54
+ @rows = rows.collect.with_index do |row, index|
55
+ SpreadsheetGoodies::Row.new(@header_row, index+1, self, *row)
56
+ end
57
+
58
+ self
59
+ end
60
+
61
+ # Reads a GoogleDrive::Spreadsheet object from Google Drive, using the
62
+ # google_drive gem. Documentation for this class can be found here:
63
+ # https://www.rubydoc.info/github/gimite/google-drive-ruby/GoogleDrive/Spreadsheet
64
+ def underlying_adapter_worksheet
65
+ @underlying_adapter_worksheet ||= Connector.new.read_worksheet(@spreadsheet_key, @worksheet_title)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module SpreadsheetGoodies::GoogleDrive
3
+
4
+ # Accesses GoogleDrive and returns a SpreadsheetGoodies::GoogleDrive::Worksheet
5
+ def self.read_worksheet(spreadsheet_key:, worksheet_title:nil, num_header_rows:1)
6
+ Worksheet.new(spreadsheet_key, worksheet_title, num_header_rows)
7
+ end
8
+
9
+ end
10
+
11
+ # loads all files in google_drive folder
12
+ Dir[File.join(File.dirname(__FILE__), "google_drive/**/*.rb")].each { |f| require f }
@@ -0,0 +1,33 @@
1
+ module SpreadsheetGoodies
2
+
3
+
4
+ # Sobrecarrega método [] da Array para permitir acessar células passando
5
+ # o título da coluna como índice. Ex: row['Data formal do pedido']
6
+ class Row < Array
7
+ attr_reader :header_row, :row_number, :parent_worksheet
8
+
9
+ def initialize(header_row, sheet_row_number, parent_worksheet, *args)
10
+ @header_row = header_row
11
+ @row_number = sheet_row_number
12
+ @parent_worksheet = parent_worksheet
13
+ super(args)
14
+ end
15
+
16
+ def [](locator)
17
+ cell_index = (locator.is_a?(String) ? @header_row.index(locator) : locator)
18
+
19
+ # queries local cache only
20
+ super(cell_index)
21
+ end
22
+
23
+ def []=(locator, value)
24
+ cell_index = (locator.is_a?(String) ? @header_row.index(locator) : locator)
25
+
26
+ # propagates change to real worksheet
27
+ @parent_worksheet.write_to_cell(@row_number, cell_index+1, value)
28
+
29
+ # updates local cache
30
+ super(cell_index, value)
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module SpreadsheetGoodies
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
@@ -1,7 +1,10 @@
1
+ module SpreadsheetGoodies
2
+
3
+ end
4
+
1
5
  require 'spreadsheet_goodies/version'
2
- require 'spreadsheet_goodies/google_drive_worksheet'
3
- require 'spreadsheet_goodies/google_drive_connector'
4
- require 'spreadsheet_goodies/excel_worksheet'
6
+ require 'spreadsheet_goodies/google_drive'
7
+ require 'spreadsheet_goodies/excel'
5
8
  require 'roo'
6
9
 
7
10
  module SpreadsheetGoodies
@@ -31,4 +34,4 @@ module SpreadsheetGoodies
31
34
  end
32
35
  end
33
36
 
34
- end
37
+ end
@@ -6,13 +6,13 @@ require 'spreadsheet_goodies/version'
6
6
  Gem::Specification.new do |gemspec|
7
7
  gemspec.name = 'spreadsheet_goodies'
8
8
  gemspec.version = SpreadsheetGoodies::VERSION
9
- gemspec.authors = ['Ricardo Jasinski']
10
- gemspec.email = ['jasinski@solvis.com.br']
9
+ gemspec.authors = ['Ricardo Jasinski', 'Henrique Gubert']
10
+ gemspec.email = ['jasinski@solvis.com.br', 'guberthenrique@hotmail.com']
11
11
 
12
12
  gemspec.summary = "SpreadsheetGoodies is a collection of tools to help work " +
13
- "with Excel and Google Drive spreadhseets."
13
+ "with Excel and Google Drive spreadsheets."
14
14
  gemspec.description = "SpreadsheetGoodies is a collection of tools to help work " +
15
- "with Excel and Google Drive spreadhseets. It relies " +
15
+ "with Excel and Google Drive spreadsheets. It relies " +
16
16
  "on other gems to do the actual work of reading and writing to " +
17
17
  "spreadsheet documents. It main features are:"
18
18
  " * Read a spreadseet to an array of arrays, to allow accessing its data " +
@@ -38,4 +38,4 @@ Gem::Specification.new do |gemspec|
38
38
  gemspec.add_dependency 'csv', '>= 3.0.0'
39
39
  gemspec.add_dependency 'roo', '>= 1.13.2'
40
40
 
41
- end
41
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spreadsheet_goodies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ricardo Jasinski
8
+ - Henrique Gubert
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2018-07-09 00:00:00.000000000 Z
12
+ date: 2018-08-17 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
@@ -109,10 +110,11 @@ dependencies:
109
110
  - !ruby/object:Gem::Version
110
111
  version: 1.13.2
111
112
  description: 'SpreadsheetGoodies is a collection of tools to help work with Excel
112
- and Google Drive spreadhseets. It relies on other gems to do the actual work of
113
+ and Google Drive spreadsheets. It relies on other gems to do the actual work of
113
114
  reading and writing to spreadsheet documents. It main features are:'
114
115
  email:
115
116
  - jasinski@solvis.com.br
117
+ - guberthenrique@hotmail.com
116
118
  executables: []
117
119
  extensions: []
118
120
  extra_rdoc_files: []
@@ -124,7 +126,6 @@ files:
124
126
  - ".travis.yml"
125
127
  - Gemfile
126
128
  - LICENSE
127
- - LICENSE.txt
128
129
  - README.md
129
130
  - Rakefile
130
131
  - bin/console
@@ -132,13 +133,16 @@ files:
132
133
  - builds/spreadsheet_goodies-0.0.1.gem
133
134
  - builds/spreadsheet_goodies-0.0.2.gem
134
135
  - builds/spreadsheet_goodies-0.0.3.gem
136
+ - builds/spreadsheet_goodies-0.0.4.gem
135
137
  - lib/spreadsheet_goodies.rb
136
- - lib/spreadsheet_goodies/excel_workbook_builder.rb
137
- - lib/spreadsheet_goodies/excel_worksheet.rb
138
- - lib/spreadsheet_goodies/excel_worksheet_row.rb
139
- - lib/spreadsheet_goodies/google_drive_connector.rb
140
- - lib/spreadsheet_goodies/google_drive_worksheet.rb
141
- - lib/spreadsheet_goodies/google_drive_worksheet_row.rb
138
+ - lib/spreadsheet_goodies/abstract_base_worksheet.rb
139
+ - lib/spreadsheet_goodies/excel.rb
140
+ - lib/spreadsheet_goodies/excel/workbook_builder.rb
141
+ - lib/spreadsheet_goodies/excel/worksheet.rb
142
+ - lib/spreadsheet_goodies/google_drive.rb
143
+ - lib/spreadsheet_goodies/google_drive/connector.rb
144
+ - lib/spreadsheet_goodies/google_drive/worksheet.rb
145
+ - lib/spreadsheet_goodies/row.rb
142
146
  - lib/spreadsheet_goodies/version.rb
143
147
  - spreadsheet_goodies.gemspec
144
148
  homepage: https://github.com/ricardo-jasinski/spreadsheet_goodies
@@ -162,9 +166,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
166
  version: '0'
163
167
  requirements: []
164
168
  rubyforge_project:
165
- rubygems_version: 2.6.6
169
+ rubygems_version: 2.6.8
166
170
  signing_key:
167
171
  specification_version: 4
168
172
  summary: SpreadsheetGoodies is a collection of tools to help work with Excel and Google
169
- Drive spreadhseets.
173
+ Drive spreadsheets.
170
174
  test_files: []
data/LICENSE.txt DELETED
@@ -1,24 +0,0 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <http://unlicense.org>
@@ -1,78 +0,0 @@
1
- require 'axlsx'
2
-
3
- # A set of methods to help create and format Excel workbooks
4
- class SpreadsheetGoodies::ExcelWorkbookBuilder
5
- attr_reader :current_sheet
6
-
7
- def initialize(output_file_pathname)
8
- @output_file_pathname = output_file_pathname
9
- @axlsx_package = Axlsx::Package.new
10
- end
11
-
12
- # The underlying Axlsx::Workbook object
13
- def workbook
14
- @axlsx_package.workbook
15
- end
16
-
17
- def add_worksheet(sheet_name)
18
- @current_sheet = @workbook.add_worksheet(name: sheet_name)
19
- setup_current_sheet_styles
20
- freeze_top_row
21
- @current_sheet
22
- end
23
-
24
- # Add a row to the current sheet using the data row style
25
- def add_data_row(row_values)
26
- @current_sheet.add_row(row_values, style: @data_row_style)
27
- end
28
-
29
- # Add a row to the current sheet using the header row style
30
- def add_header_row(row_values)
31
- @current_sheet.add_row(row_values, style: @header_row_style)
32
- end
33
-
34
- # Add filter and sorting controls to a sheet.
35
- def setup_auto_filter(sheet=nil)
36
- worksheet = sheet || @current_sheet
37
- top_left_cell_label = worksheet.dimension.first_cell_reference
38
- bottom_right_cell_label = worksheet.dimension.last_cell_reference
39
- filter_range = "#{top_left_cell_label}:#{bottom_right_cell_label}"
40
- worksheet.auto_filter = filter_range
41
- end
42
-
43
- def write_to_file
44
- @axlsx_package.serialize(@output_file_pathname)
45
- end
46
-
47
- def freeze_top_row
48
- @current_sheet.sheet_view.pane do |pane|
49
- pane.top_left_cell = 'A2'
50
- pane.state = :frozen_split
51
- pane.y_split = 1
52
- pane.active_pane = :bottom_right
53
- end
54
- end
55
-
56
- private
57
-
58
- def setup_current_sheet_styles
59
- @header_row_style = @current_sheet.styles.add_style(
60
- alignment: {horizontal: :center, vertical: :center},
61
- font_name: 'Calibri',
62
- bg_color: 'FFDDDDDD',
63
- b: true,
64
- )
65
-
66
- @header_row_sodexo_style = @current_sheet.styles.add_style(
67
- alignment: {horizontal: :center, vertical: :center},
68
- font_name: 'Calibri',
69
- bg_color: '002060',
70
- fg_color: 'FFFFFF',
71
- b: true,
72
- )
73
-
74
- @data_row_style = @current_sheet.styles.add_style(
75
- font_name: 'Calibri',
76
- )
77
- end
78
- end
@@ -1,68 +0,0 @@
1
- require_relative 'excel_worksheet_row'
2
-
3
- # A cached copy of an Excel worksheet (a single workbook "tab"), stored as an
4
- # array of arrays. Individual cells can be accessed by column title, e.g.:
5
- # worksheet[0]['A column title']
6
- class SpreadsheetGoodies::ExcelWorksheet
7
- attr_reader :rows, :workbook, :worksheet
8
-
9
- # @param workbook_file_pathname [String] Full path and filename to Excel workbook document
10
- # @param worksheet_title_or_index [String|Integer] Sheet name or index (zero-based)
11
- # within the workbook. Optional; if unspecified, the first sheet will be used.
12
- # @param num_header_rows [Integer] Number of rows at the top of the sheet that
13
- # contain headers or stuff other than data. Optional; if unspecified, assumes
14
- # that a single top row contains the header and all rows below are data.
15
- def initialize(workbook_file_pathname, worksheet_title_or_index=0, num_header_rows=1)
16
- @worksheet_title = worksheet_title_or_index
17
- @num_header_rows = num_header_rows
18
-
19
- @workbook = case workbook_file_pathname.to_s
20
- when /\.xls[^x]/ then Roo::Excel.new(workbook_file_pathname, file_warning: :ignore)
21
- when /\.xlsx/ then Roo::Excelx.new(workbook_file_pathname, file_warning: :ignore)
22
- end
23
-
24
- @worksheet = @workbook.sheet(worksheet_title_or_index)
25
-
26
- @header_row = @worksheet.row(num_header_rows)
27
-
28
- # Reads all the worksheet rows, one at a time, using Workshete#row (note: reading
29
- # them all at once using Worksheet#parse didn't work because the first row
30
- # was missed some times.)
31
- rows = (1..@worksheet.last_row).map {|row_number| @worksheet.row(row_number) }
32
-
33
- # Create the rows cache, made of instances of ExcelWorksheetRow
34
- @rows = rows.collect.with_index do |row, index|
35
- ExcelWorksheetRow.new(@header_row, index+1, *row)
36
- end
37
-
38
- self
39
- end
40
-
41
- def [](index)
42
- @rows[index]
43
- end
44
-
45
- # Return only the rows that contain data (excludes the header rows)
46
- def data_rows
47
- @rows[@num_header_rows..-1]
48
- end
49
-
50
- # Finds and returns the first row that contains cell_value at the given column
51
- def find_row_by_column_value(column_title, cell_value)
52
- data_rows.each do |row|
53
- return row if row[column_title] == cell_value
54
- end
55
-
56
- nil
57
- end
58
-
59
- def add_row(row_data)
60
- last_row_number = @rows.count
61
- @rows << ExcelWorksheetRow.new(@header_row, last_row_number+1, *row_data)
62
- end
63
-
64
- def spreadsheet
65
- @workbook
66
- end
67
-
68
- end
@@ -1,30 +0,0 @@
1
- class SpreadsheetGoodies::ExcelWorksheet
2
-
3
- # Overload Array#[] to allow reading a cell's contents using its column title
4
- # as index, e.g.: row['Column Title']
5
- class ::ExcelWorksheetRow < Array
6
- attr_reader :header_row, :row_number
7
-
8
- # @param header_row [Array] The header cells of the sheet from were the row was taken
9
- # @param sheet_row_number [Integer] The original row number of the row in the
10
- # sheet from where the row was taken. Row numebrs follow the spreadshet convention,
11
- # i.e., starting from 1.
12
- def initialize(header_row, sheet_row_number, *args)
13
- @header_row = header_row
14
- @row_number = sheet_row_number
15
- super(args)
16
- end
17
-
18
- # @param locator [Integer|String] The index of a row element. Can be a string
19
- # (a column title) or an integer (starting at 0 for the first cell).
20
- # @return [String]
21
- def [](locator)
22
- if locator.is_a?(String)
23
- return self[ @header_row.index(locator) ]
24
- else
25
- return super(locator)
26
- end
27
- end
28
- end
29
-
30
- end
@@ -1,93 +0,0 @@
1
- require 'google_drive'
2
- require 'googleauth'
3
-
4
- class SpreadsheetGoodies::GoogleDriveConnector
5
- attr_reader :logger, :session
6
-
7
- def initialize
8
- @logger = Logger.new(STDOUT)
9
- @session = log_into_google_drive
10
- raise 'Session not found!' unless @session
11
- end
12
-
13
- def log_into_google_drive
14
- case SpreadsheetGoodies.configuration.login_method
15
- when :service_account then log_into_google_drive_via_service_account
16
- when :oauth2 then log_into_google_drive_via_oauth2
17
- end
18
- end
19
-
20
- def read_worksheet(spreadsheet_key, worksheet_title=nil)
21
- puts "Reading sheet '#{worksheet_title}' from Google Drive..."
22
- spreadsheet = @session.spreadsheet_by_key(spreadsheet_key)
23
-
24
- if worksheet_title
25
- worksheet = spreadsheet.worksheet_by_title(worksheet_title)
26
- if worksheet.nil?
27
- raise "Worksheet named '#{worksheet_title}' not found in spreadsheet."
28
- end
29
- else
30
- worksheet = spreadsheet.worksheets.first
31
- end
32
-
33
- worksheet
34
- end
35
-
36
- def read_worksheet_as_string(spreadsheet_key, worksheet_title)
37
- worksheet = read_worksheet(spreadsheet_key, worksheet_title)
38
- contents = worksheet.export_as_string.force_encoding('utf-8')
39
- puts 'Spreadhseet read successfully.'
40
- contents
41
- end
42
-
43
- private
44
-
45
- # Authenticate with Google Drive via OAuth2.
46
- # Your OAuth2 ids can be accessed at https://console.developers.google.com/apis/credentials,
47
- # logged with your Google account in your custom project. The keys are at
48
- # 'API manager' > 'Credentials'. CLIENT_ID and CLIENT_SECRET are available
49
- # from the credentials page. The REFRESH_TOKEN must be obtained uncommenting
50
- # some rows from the method below and obtaining an access_token via a browser
51
- # logged in to your google account.
52
- def log_into_google_drive_via_oauth2
53
- credentials = Google::Auth::UserRefreshCredentials.new(
54
- client_id: SpreadsheetGoodies.configuration.google_client_id,
55
- client_secret: SpreadsheetGoodies.configuration.client_secret,
56
- scope: [
57
- 'https://www.googleapis.com/auth/drive',
58
- 'https://spreadsheets.google.com/feeds/',
59
- ],
60
- redirect_uri: 'urn:ietf:wg:oauth:2.0:oob'
61
- )
62
- credentials.refresh_token = SpreadsheetGoodies.configuration.refresh_token if SpreadsheetGoodies.configuration.refresh_token
63
-
64
- # Uncomment the rows below to obtain the refresh_token:
65
- # print("1. Open this page:\n%s\n\n" % credentials.authorization_uri)
66
- # print("2. Enter the authorization code shown in the page: ")
67
- # credentials.code = $stdin.gets.chomp
68
- # credentials.fetch_access_token!
69
- # puts credentials.refresh_token
70
- # debugger
71
-
72
- begin
73
- credentials.fetch_access_token!
74
- rescue Faraday::ConnectionFailed
75
- logger.info 'Error logging into Google Drive. Is your internet connection down?'
76
- exit
77
- end
78
-
79
- session = GoogleDrive::Session.from_credentials(credentials)
80
- end
81
-
82
- # Authenticate with Google Drive via a service account.
83
- # Your service accounts can be accessed at https://console.developers.google.com/apis/credentials,
84
- # logged with your Google account in your custom project.
85
- # The service accounts are at 'IAM and administrator' > 'Service accounts'.
86
- # The keys are at 'API manager' > 'Credentials'.
87
- def log_into_google_drive_via_service_account
88
- session = GoogleDrive::Session.from_service_account_key(
89
- StringIO.new(SpreadsheetGoodies.configuration.service_account_key_json)
90
- )
91
- session
92
- end
93
- end
@@ -1,68 +0,0 @@
1
- require_relative 'google_drive_worksheet_row'
2
- require 'csv'
3
-
4
- # A cached copy of a Google Spreadsheets worksheet (i.e., a single workbook "tab"),
5
- # stored as an array of arrays. Individual cells can be accessed by column title.
6
- # Example:
7
- # worksheet[0]['A column title']
8
- class SpreadsheetGoodies::GoogleDriveWorksheet
9
- attr_reader :rows, :header_row
10
-
11
- # @param worksheet_title_or_index [String] Sheet name; if unspecified, the
12
- # first sheet will be used.
13
- # @param num_header_rows [Integer] Number of rows at the top of the sheet that
14
- # contain headers or stuff other than data. Optional; if unspecified, assumes
15
- # that a single top row contains the header and all rows below are data.
16
- def initialize(spreadsheet_key, worksheet_title=nil, num_header_rows=1)
17
- @spreadsheet_key = spreadsheet_key
18
- @worksheet_title = worksheet_title
19
- @num_header_rows = num_header_rows
20
- end
21
-
22
- # Reads sheet contents and caches it into instance variables to so that it
23
- # can be accessed later without accessing Google Drive.
24
- def read_from_google_drive
25
- worksheet_contents = read_worksheet.export_as_string.force_encoding('utf-8')
26
-
27
- rows = CSV::parse(worksheet_contents)
28
-
29
- if SpreadsheetGoodies.configuration.strip_values_on_read
30
- rows = rows.map do |row|
31
- row.map {|element| element.try(:strip) }
32
- end
33
- end
34
-
35
- @header_row = rows[@num_header_rows-1]
36
- @rows = rows.collect.with_index do |row, index|
37
- GoogleDriveWorksheetRow.new(@header_row, index+1, *row)
38
- end
39
-
40
- self
41
- end
42
-
43
- def [](index)
44
- @rows[index]
45
- end
46
-
47
- # Return only the rows that contain data (excludes the header rows)
48
- def data_rows
49
- @rows[@num_header_rows..-1]
50
- end
51
-
52
- # Finds and returns the first row that contains cell_value at the given column
53
- def find_row_by_column_value(column_title, cell_value)
54
- data_rows.each do |row|
55
- return row if row[column_title] == cell_value
56
- end
57
-
58
- nil
59
- end
60
-
61
- # Reads a GoogleDrive::Spreadsheet object from Google Drive. The sheet object
62
- # can be used to write data to the online spreadhseet, as we don't yet provide
63
- # the helper methods for our users to do it via our public interface.
64
- def read_worksheet
65
- connector = SpreadsheetGoodies::GoogleDriveConnector.new
66
- connector.read_worksheet(@spreadsheet_key, @worksheet_title)
67
- end
68
- end
@@ -1,23 +0,0 @@
1
- class SpreadsheetGoodies::GoogleDriveWorksheet
2
-
3
- # Sobrecarrega método [] da Array para permitir acessar células passando
4
- # o título da coluna como índice. Ex: row['Data formal do pedido']
5
- class ::GoogleDriveWorksheetRow < Array
6
- attr_reader :header_row, :row_number
7
-
8
- def initialize(header_row, sheet_row_number, *args)
9
- @header_row = header_row
10
- @row_number = sheet_row_number
11
- super(args)
12
- end
13
-
14
- def [](locator)
15
- if locator.is_a?(String)
16
- return self[ @header_row.index(locator) ]
17
- else
18
- return super(locator)
19
- end
20
- end
21
- end
22
-
23
- end