excel_utils 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13a477a8679749794ff54f4bb0a76c653b354be459d50ed1d7c89a6de8a32509
4
- data.tar.gz: 920a86b0b1fb587105d7fc78609cf5bdd98a231aa6bee22726ebdfc090b3daca
3
+ metadata.gz: 70807b62912a2e82da93e85e0275b6f3f6b6a8f010f70a7924546f31f0d93087
4
+ data.tar.gz: 6b78f90fd4174174044c9464299bf645bc13619251b1fde6f0ac9079a00b6d14
5
5
  SHA512:
6
- metadata.gz: cfb43adfda51aa543e1dd13e00f952b9eb0d2c9cec9dd7b41119eea1b2e0b2db433e47040c224dda7069f0ff0ec0b6d17f43f215f50e60a66db4545ecc740f5a
7
- data.tar.gz: c1f424a05025b504b92cd5bf5b67f53c60129ad1973045daf263acf837301e61cc2681a9ce87a3ad90ba20e82a8ac1ef51891e9b2f6d387f4c3756fa6145a74f
6
+ metadata.gz: c8619bbea23bbad8b25c4014e0ead0bb8b81d901dce0c0e355e47b3b1fd2742f7c307d25980afc7d5066515ef33547d89d8c71df586ffe06bcdd3bc5dc6f9de4
7
+ data.tar.gz: 98a84f2fa77259e4ce3bbb691d267d4427e7e887eb0a41385ee7b82e59b7e1e6d06e1da333a257f3cdc9c54768a530c7c709608c3e8b89726c89ae61185b46fa
data/excel_utils.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_runtime_dependency 'inflecto', '~> 0.0'
22
- spec.add_runtime_dependency 'roo', '~> 2.7'
23
- spec.add_runtime_dependency 'roo-xls', '~> 1.1'
22
+ spec.add_runtime_dependency 'roo', '~> 2.8'
23
+ spec.add_runtime_dependency 'roo-xls', '~> 1.2'
24
24
  spec.add_runtime_dependency 'write_xlsx', '~> 0.85'
25
+ spec.add_runtime_dependency 'nesquikcsv', '~> 0.1'
25
26
 
26
27
  spec.add_development_dependency 'rake', '~> 12.0'
27
28
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
data/lib/excel_utils.rb CHANGED
@@ -4,20 +4,30 @@ require 'roo'
4
4
  require 'roo-xls'
5
5
  require 'write_xlsx'
6
6
  require 'inflecto'
7
+ require 'nesquikcsv'
7
8
 
8
9
  require_relative 'excel_utils/version'
9
- require_relative 'excel_utils/workbook'
10
- require_relative 'excel_utils/sheet'
10
+ require_relative 'excel_utils/workbooks/csv'
11
+ require_relative 'excel_utils/workbooks/excel'
12
+ require_relative 'excel_utils/sheets/base'
13
+ require_relative 'excel_utils/sheets/csv'
14
+ require_relative 'excel_utils/sheets/excel'
15
+ require_relative 'excel_utils/sheets/excel_stream'
11
16
  require_relative 'excel_utils/writer'
12
17
 
13
18
  module ExcelUtils
14
-
19
+
15
20
  def self.read(filename, **options)
16
- Workbook.new filename, **options
21
+ extension = options.fetch(:extension, File.extname(filename)[1..-1])
22
+ if extension == 'csv'
23
+ Workbooks::CSV.new(filename, **options)
24
+ else
25
+ Workbooks::Excel.new(filename, **options)
26
+ end
17
27
  end
18
28
 
19
29
  def self.write(filename, data)
20
30
  Writer.write filename, data
21
31
  end
22
32
 
23
- end
33
+ end
@@ -0,0 +1,41 @@
1
+ module ExcelUtils
2
+ module Sheets
3
+ class Base
4
+
5
+ include Enumerable
6
+
7
+ attr_reader :name, :normalize_column_names
8
+
9
+ def initialize(name:, normalize_column_names: false)
10
+ @name = name
11
+ @normalize_column_names = normalize_column_names
12
+ end
13
+
14
+ def column_names
15
+ @column_names ||= normalize_column_names ? normalize_columns(first_row) : first_row
16
+ end
17
+
18
+ def each
19
+ if column_names.any?
20
+ each_row do |row|
21
+ break if empty_row? row
22
+ yield Hash[column_names.zip(row)]
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def normalize_columns(names)
30
+ names.map do |name|
31
+ Inflecto.underscore(name.strip.gsub(' ', '_')).to_sym
32
+ end
33
+ end
34
+
35
+ def empty_row?(row)
36
+ row.all? { |cell| cell.to_s.strip.empty? }
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ module ExcelUtils
2
+ module Sheets
3
+ class CSV < Base
4
+
5
+ def initialize(filename:, **options)
6
+ super(**options)
7
+ @filename = filename
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :filename
13
+
14
+ def first_row
15
+ NesquikCSV.open(filename) { |csv| csv.readline } || []
16
+ end
17
+
18
+ def each_row
19
+ first = true
20
+ NesquikCSV.foreach(filename) do |row|
21
+ yield row unless first
22
+ first = false
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module ExcelUtils
2
+ module Sheets
3
+ class Excel < Base
4
+
5
+ def initialize(spreadsheet:, **options)
6
+ super(**options)
7
+ @spreadsheet = spreadsheet
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :spreadsheet
13
+
14
+ def first_row
15
+ with_sheet do |sheet|
16
+ sheet.first_row ? sheet.row(sheet.first_row) : []
17
+ end
18
+ end
19
+
20
+ def each_row
21
+ with_sheet do |sheet|
22
+ (sheet.first_row + 1).upto(sheet.last_row) do |i|
23
+ yield sheet.row(i)
24
+ end
25
+ end
26
+ end
27
+
28
+ def with_sheet
29
+ yield spreadsheet.sheet(name)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module ExcelUtils
2
+ module Sheets
3
+ class ExcelStream < Base
4
+
5
+ def initialize(spreadsheet:, **options)
6
+ super(**options)
7
+ @spreadsheet = spreadsheet
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :spreadsheet
13
+
14
+ def first_row
15
+ row = sheet.each_row_streaming(pad_cells: true, max_rows: 0).first || []
16
+ normalize_row row
17
+ end
18
+
19
+ def each_row
20
+ sheet.each_row_streaming(pad_cells: true, offset: 1) do |row|
21
+ yield normalize_row(row)
22
+ end
23
+ end
24
+
25
+ def normalize_row(row)
26
+ row.map { |cell| cell ? cell.value : cell }
27
+ end
28
+
29
+ def sheet
30
+ spreadsheet.sheet name
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module ExcelUtils
2
- VERSION = '1.0.0'
3
- end
2
+ VERSION = '1.3.0'
3
+ end
@@ -0,0 +1,36 @@
1
+ module ExcelUtils
2
+ module Workbooks
3
+ class CSV
4
+
5
+ SHEET_NAME = 'default'.freeze
6
+
7
+ attr_reader :filename, :normalize_column_names
8
+
9
+ def initialize(filename, normalize_column_names: false)
10
+ @filename = filename
11
+ @normalize_column_names = normalize_column_names
12
+
13
+ @sheet = Sheets::CSV.new name: SHEET_NAME,
14
+ normalize_column_names: normalize_column_names,
15
+ filename: filename
16
+ end
17
+
18
+ def sheets
19
+ [sheet]
20
+ end
21
+
22
+ def [](sheet_name)
23
+ sheet_name == SHEET_NAME ? sheet : nil
24
+ end
25
+
26
+ def to_h
27
+ {SHEET_NAME => sheet.to_a}
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :sheet
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ module ExcelUtils
2
+ module Workbooks
3
+ class Excel
4
+
5
+ attr_reader :filename, :normalize_column_names
6
+
7
+ def initialize(filename, normalize_column_names: false, extension: nil)
8
+ @filename = filename
9
+ @normalize_column_names = normalize_column_names
10
+ @spreadsheet = Roo::Spreadsheet.open filename, extension: extension
11
+ end
12
+
13
+ def sheets
14
+ @sheets ||= spreadsheet.sheets.map do |name|
15
+ sheet_class.new name: name,
16
+ normalize_column_names: normalize_column_names,
17
+ spreadsheet: spreadsheet
18
+ end
19
+ end
20
+
21
+ def [](sheet_name)
22
+ sheets.detect { |sheet| sheet.name == sheet_name }
23
+ end
24
+
25
+ def to_h
26
+ sheets.each_with_object({}) do |sheet, hash|
27
+ hash[sheet.name] = sheet.to_a
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :spreadsheet
34
+
35
+ def sheet_class
36
+ @sheet_class ||= spreadsheet.respond_to?(:each_row_streaming) ? Sheets::ExcelStream : Sheets::Excel
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -1,20 +1,24 @@
1
1
  module ExcelUtils
2
2
  class Writer
3
3
 
4
- TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
4
+ DEFAULT_SHEET_NAME = 'Sheet1'.freeze
5
+
6
+ TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'.freeze
5
7
 
6
8
  EXCEL_FORMATS = {
7
9
  date: 'yyyy-mm-dd',
8
10
  date_time: 'yyyy-mm-dd hh:mm:ss'
9
- }
11
+ }.freeze
10
12
 
11
13
  class << self
12
-
14
+
13
15
  def write(filename, data)
14
16
  workbook = WriteXLSX.new filename, strings_to_urls: false
15
17
 
16
18
  formats = add_formats workbook
17
19
 
20
+ data = {DEFAULT_SHEET_NAME => data} if data.is_a? Array
21
+
18
22
  data.each do |sheet_name, sheet_data|
19
23
  add_sheet workbook, sheet_name, sheet_data, formats
20
24
  end
@@ -34,18 +38,23 @@ module ExcelUtils
34
38
  sheet = workbook.add_worksheet sheet_name
35
39
 
36
40
  if sheet_data.any?
37
- header = sheet_data.first.keys
41
+ header = sheet_data.flat_map(&:keys).uniq
38
42
  sheet.write_row 0, 0, header.map(&:to_s)
39
43
 
40
44
  sheet_data.each_with_index do |row, r|
41
- header.each_with_index do |column, c|
45
+ row_index = r + 1
46
+ header.each_with_index do |column, col_index|
42
47
  if row[column]
43
- if row[column].respond_to? :to_time
48
+ if row[column].is_a?(String) || row[column].is_a?(Array)
49
+ sheet.write_string row_index, col_index, row[column]
50
+
51
+ elsif row[column].respond_to? :to_time
44
52
  time = row[column].to_time
45
53
  type = date?(time) ? :date : :date_time
46
- sheet.write_date_time r + 1, c, time.to_time.strftime(TIME_FORMAT), formats[type]
54
+ sheet.write_date_time row_index, col_index, time.to_time.strftime(TIME_FORMAT), formats[type]
55
+
47
56
  else
48
- sheet.write r + 1, c, row[column]
57
+ sheet.write row_index, col_index, row[column]
49
58
  end
50
59
  end
51
60
  end
@@ -3,4 +3,27 @@ require 'minitest/autorun'
3
3
  require 'minitest/colorin'
4
4
  require 'pry-nav'
5
5
 
6
- require 'excel_utils'
6
+ require 'excel_utils'
7
+
8
+ RESOURCES_PATH = File.expand_path '../resources', __FILE__
9
+
10
+ TMP_PATH = File.expand_path '../../tmp', __FILE__
11
+ FileUtils.mkpath TMP_PATH unless Dir.exists? TMP_PATH
12
+
13
+ class Minitest::Test
14
+
15
+ def resource_path(relative_path)
16
+ File.join RESOURCES_PATH, relative_path
17
+ end
18
+
19
+ def tmp_path(relative_path)
20
+ File.join TMP_PATH, relative_path
21
+ end
22
+
23
+ end
24
+
25
+ Minitest.after_run do
26
+ Dir.glob(File.join(TMP_PATH, '*')).each do |filename|
27
+ FileUtils.remove_file filename
28
+ end
29
+ end
data/spec/read_spec.rb CHANGED
@@ -2,104 +2,151 @@ require 'minitest_helper'
2
2
 
3
3
  describe ExcelUtils, 'Read' do
4
4
 
5
- def expected_rows(sheet)
6
- rows_by_sheet[sheet.name].map { |r| Hash[columns_by_sheet[sheet.name].zip(r)] }
7
- end
5
+ Dir.glob(File.join(RESOURCES_PATH, 'basic.*')).each do |filename|
8
6
 
9
- let :rows_by_sheet do
10
- {
11
- 'Sheet1' => [
12
- [1.0, 'some text'],
13
- [2.0, 1.35],
14
- [3.0, Date.parse('2019-08-17')],
15
- [4.0, nil]
16
- ],
17
- 'Sheet2' => [
18
- [123.0, 'Text 1'],
19
- [456.0, 'Text 2']
20
- ],
21
- 'Sheet3' => []
22
- }
23
- end
7
+ [true, false].each do |normalize_column_names|
8
+
9
+ describe File.basename(filename), "Workbook (normalize_column_names: #{normalize_column_names})" do
10
+
11
+ let(:workbook) { ExcelUtils.read filename, normalize_column_names: normalize_column_names }
12
+
13
+ let(:sheet) { workbook.sheets.first }
24
14
 
25
- ['xls', 'xlsx'].each do |extension|
15
+ let(:csv?) { File.extname(filename) == '.csv' }
26
16
 
27
- describe extension do
28
-
29
- let(:filename) { File.expand_path "../sample.#{extension}", __FILE__ }
17
+ let(:expected_sheet_name) { csv? ? 'default' : 'Sheet1' }
30
18
 
31
- describe 'Original column names' do
19
+ let(:column_a) { normalize_column_names ? :column_a : 'Column A' }
20
+
21
+ let(:column_b) { normalize_column_names ? :column_b : 'Column B' }
22
+
23
+ let(:expected_columns) { [column_a, column_b] }
24
+
25
+ let :expected_rows do
26
+ if csv?
27
+ [
28
+ {column_a => '1', column_b => 'some text'},
29
+ {column_a => '2', column_b => '1,35'},
30
+ {column_a => '3', column_b => '17/08/2019'},
31
+ {column_a => '4', column_b => nil}
32
+ ]
33
+ else
34
+ [
35
+ {column_a => 1, column_b => 'some text'},
36
+ {column_a => 2, column_b => 1.35},
37
+ {column_a => 3, column_b => Date.parse('2019-08-17')},
38
+ {column_a => 4, column_b => nil}
39
+ ]
40
+ end
41
+ end
32
42
 
33
- let(:workbook) { ExcelUtils.read filename }
34
-
35
- let :columns_by_sheet do
36
- {
37
- 'Sheet1' => ['Column A', 'Column B'],
38
- 'Sheet2' => ['ID', 'Value'],
39
- 'Sheet3' => []
40
- }
43
+ it 'filename' do
44
+ workbook.filename.must_equal filename
45
+ end
46
+
47
+ it 'normalize_column_names' do
48
+ workbook.normalize_column_names.must_equal normalize_column_names
49
+ end
50
+
51
+ it 'sheets' do
52
+ workbook.sheets.count.must_equal 1
53
+ workbook[workbook.sheets.first.name].must_equal sheet
41
54
  end
42
55
 
43
56
  it 'to_h' do
44
- workbook.to_h.must_equal 'Sheet1' => expected_rows(workbook['Sheet1']),
45
- 'Sheet2' => expected_rows(workbook['Sheet2']),
46
- 'Sheet3' => []
57
+ workbook.to_h.must_equal workbook.sheets.first.name => sheet.to_a
47
58
  end
48
59
 
49
- ['Sheet1', 'Sheet2', 'Sheet3'].each do |sheet_name|
60
+ describe 'Sheet' do
50
61
 
51
- describe sheet_name do
62
+ it 'name' do
63
+ sheet.name.must_equal expected_sheet_name
64
+ end
52
65
 
53
- let(:sheet) { workbook[sheet_name] }
66
+ it 'normalize_column_names' do
67
+ sheet.normalize_column_names.must_equal workbook.normalize_column_names
68
+ end
54
69
 
55
- it 'Column names' do
56
- sheet.column_names.must_equal columns_by_sheet[sheet_name]
57
- end
70
+ it 'column_names' do
71
+ sheet.column_names.must_equal expected_columns
72
+ end
58
73
 
59
- it 'Rows' do
60
- sheet.to_a.must_equal expected_rows(sheet)
61
- end
74
+ it 'count' do
75
+ sheet.count.must_equal 4
76
+ end
62
77
 
78
+ it 'to_a' do
79
+ sheet.to_a.must_equal expected_rows
63
80
  end
64
81
 
65
82
  end
66
83
 
67
84
  end
68
85
 
69
- describe 'Normalized column names' do
86
+ end
87
+
88
+ end
70
89
 
71
- let(:workbook) { ExcelUtils.read filename, normalize_column_names: true }
72
-
73
- let :columns_by_sheet do
74
- {
75
- 'Sheet1' => [:column_a, :column_b],
76
- 'Sheet2' => [:id, :value],
77
- 'Sheet3' => []
78
- }
79
- end
90
+ it 'empty.csv' do
91
+ workbook = ExcelUtils.read resource_path('empty.csv')
80
92
 
81
- ['Sheet1', 'Sheet2', 'Sheet3'].each do |sheet_name|
93
+ workbook.sheets.map(&:name).must_equal ['default']
82
94
 
83
- describe sheet_name do
95
+ workbook['default'].column_names.must_equal []
96
+ workbook['default'].to_a.must_equal []
84
97
 
85
- let(:sheet) { workbook[sheet_name] }
98
+ workbook.to_h.must_equal 'default' => []
99
+ end
86
100
 
87
- it 'Column names' do
88
- sheet.column_names.must_equal columns_by_sheet[sheet_name]
89
- end
101
+ it 'only_headers.csv' do
102
+ workbook = ExcelUtils.read resource_path('only_headers.csv')
90
103
 
91
- it 'Rows' do
92
- sheet.to_a.must_equal expected_rows(sheet)
93
- end
104
+ workbook.sheets.map(&:name).must_equal ['default']
94
105
 
95
- end
106
+ workbook['default'].column_names.must_equal ['ID', 'Value']
107
+ workbook['default'].to_a.must_equal []
96
108
 
97
- end
109
+ workbook.to_h.must_equal 'default' => []
110
+ end
98
111
 
99
- end
112
+ it 'custom_extension.tmp' do
113
+ workbook = ExcelUtils.read resource_path('custom_extension.tmp'), extension: 'xlsx'
100
114
 
101
- end
115
+ expected_rows = [
116
+ {'ID' => 1, 'Value' => 'Text 1'},
117
+ {'ID' => 2, 'Value' => 'Text 2'}
118
+ ]
119
+
120
+ workbook.sheets.map(&:name).must_equal ['Sheet1']
121
+
122
+ workbook['Sheet1'].column_names.must_equal ['ID', 'Value']
123
+ workbook['Sheet1'].to_a.must_equal expected_rows
124
+
125
+ workbook.to_h.must_equal 'Sheet1' => expected_rows
126
+ end
127
+
128
+ it 'multiple.xlsx' do
129
+ workbook = ExcelUtils.read resource_path('multiple.xlsx'), normalize_column_names: true
130
+
131
+ workbook.sheets.map(&:name).must_equal ['Sheet1', 'Sheet2', 'Sheet3']
132
+
133
+ expected_rows_sheet_1 = [
134
+ {id: 1, value: 'Text 1'},
135
+ {id: 2, value: 'Text 2'}
136
+ ]
137
+
138
+ workbook['Sheet1'].column_names.must_equal [:id, :value]
139
+ workbook['Sheet1'].to_a.must_equal expected_rows_sheet_1
140
+
141
+ workbook['Sheet2'].column_names.must_equal [:id, :value]
142
+ workbook['Sheet2'].to_a.must_equal []
143
+
144
+ workbook['Sheet3'].column_names.must_equal []
145
+ workbook['Sheet3'].to_a.must_equal []
102
146
 
147
+ workbook.to_h.must_equal 'Sheet1' => expected_rows_sheet_1,
148
+ 'Sheet2' => [],
149
+ 'Sheet3' => []
103
150
  end
104
151
 
105
152
  end
@@ -0,0 +1,5 @@
1
+ Column A,Column B
2
+ 1,some text
3
+ 2,"1,35"
4
+ 3,17/08/2019
5
+ 4,
Binary file
Binary file
Binary file
Binary file
File without changes
Binary file
@@ -0,0 +1 @@
1
+ ID,Value
data/spec/write_spec.rb CHANGED
@@ -2,32 +2,95 @@ require 'minitest_helper'
2
2
 
3
3
  describe ExcelUtils do
4
4
 
5
- let(:tmp_path) { File.expand_path '../../tmp', __FILE__ }
5
+ def assert_wrote(data, expected=data)
6
+ filename = tmp_path "#{SecureRandom.uuid}.xlsx"
6
7
 
7
- let(:filename) { File.join tmp_path, "#{SecureRandom.uuid}.xlsx" }
8
+ ExcelUtils.write filename, data
9
+
10
+ workbook = ExcelUtils.read filename
11
+
12
+ expected = {'Sheet1' => expected} if expected.is_a?(Array)
13
+
14
+ workbook.to_h.must_equal expected
15
+ end
16
+
17
+ describe 'Single sheet' do
18
+
19
+ it 'Basic data' do
20
+ rows = [
21
+ {'column_a' => '1', 'column_b' => 'text 1'},
22
+ {'column_a' => '2.5', 'column_b' => nil, 'column_c' => 'text 2'},
23
+ ]
24
+
25
+ assert_wrote rows, rows.map { |r| {'column_c' => nil}.merge r }
26
+ end
27
+
28
+ it 'Symbolized column names' do
29
+ rows = [
30
+ {column_a: 'text 1'},
31
+ {column_a: 'text 2'},
32
+ ]
33
+
34
+ assert_wrote rows, rows.map { |r| {'column_a' => r[:column_a]} }
35
+ end
36
+
37
+ it 'Long values' do
38
+ long_url = 'https://external.xx.fbcdn.net/safe_image.php?d=AQCY1f77h1RtuFfa&w=720&h=720&url=https%3A%2F%2Fwww.mercadolibre.com%2Fjms%2Fmla%2Flgz%2Fbackground%2Fsession%2Farmor.c27f26204365785956aaf9b30c289d0d4b0a201b4b01dfc3d83162b82608973be9a82ac85c2097c3f23b522681adca38b143f5e1a10fda251b430051a1592bf19caba204c07179120c48d47a1ceb5a45.774f4b8036d4b909101bab92870d262a%3Fbackground%3Darmor.c27f26204365785956aaf9b30c289d0d4b0a201b4b01dfc3d83162b82608973be9a82ac85c2097c3f23b522681adca38b143f5e1a10fda251b430051a1592bf19caba204c07179120c48d47a1ceb5a45.774f4b8036d4b909101bab92870d262a%26message%3DeyJqc190eXBlIjoianNfY29va2llIiwidmFsdWUiOiJ4In0&cfs=1&_nc_hash=AQB9Sf1HyWCwCVhF'
39
+ long_text = 'AQCY1f77h1RtuFfa&w=720&h=720&url=https%3A%2F%2Fwww.mercadolibre.com%2Fjms%2Fmla%2Flgz%2Fbackground%2Fsession%2Farmor.c27f26204365785956aaf9b30c289d0d4b0a201b4b01dfc3d83162b82608973be9a82ac85c2097c3f23b522681adca38b143f5e1a10fda251b430051a1592bf19caba204c07179120c48d47a1ceb5a45.774f4b8036d4b909101bab92870d262a%3Fbackground%3Darmor.c27f26204365785956aaf9b30c289d0d4b0a201b4b01dfc3d83162b82608973be9a82ac85c2097c3f23b522681adca38b143f5e1a10fda251b430051a1592bf19caba204c07179120c48d47a1ceb5a45.774f4b8036d4b909101bab92870d262a%26message%3DeyJqc190eXBlIjoianNfY29va2llIiwidmFsdWUiOiJ4In0&cfs=1&_nc_hash=AQB9Sf1HyWCwCVhF'
40
+
41
+ rows = [
42
+ {'column_a' => long_url},
43
+ {'column_a' => long_text},
44
+ ]
45
+
46
+ assert_wrote rows
47
+ end
48
+
49
+ it 'Numbers' do
50
+ rows = [
51
+ {'column_a' => 1},
52
+ {'column_a' => 2.5},
53
+ ]
54
+
55
+ assert_wrote rows
56
+ end
57
+
58
+ it 'Date and DateTime' do
59
+ rows = [
60
+ {'column_a' => DateTime.parse('2019-05-25T16:30:00')},
61
+ {'column_a' => Date.parse('2019-07-09')},
62
+ ]
63
+
64
+ assert_wrote rows
65
+ end
66
+
67
+ it 'Objects' do
68
+ rows = [
69
+ {'column_a' => ['text', 1]},
70
+ {'column_a' => {key: 123}},
71
+ {'column_a' => Set.new(['text', 1])}
72
+ ]
73
+
74
+ assert_wrote rows, rows.map { |r| {'column_a' => r['column_a'].to_s} }
75
+ end
8
76
 
9
- before do
10
- FileUtils.rm_rf tmp_path
11
- FileUtils.mkpath tmp_path
12
77
  end
13
78
 
14
- it 'Write' do
79
+ it 'Multiple sheets' do
15
80
  data = {
16
- 'Sheet 1' => [
17
- {'column_a' => 1.5, 'column_b' => 'text 1', 'column_c' => DateTime.parse('2019-05-25T16:30:00')},
18
- {'column_b' => 'text 2', 'column_c' => Date.parse('2019-07-09'), 'column_a' => 2},
81
+ 'strings' => [
82
+ {'column_a' => 'text 1', 'column_b' => 'text 2'},
83
+ {'column_a' => 'text 3', 'column_b' => 'text 4'}
19
84
  ],
20
- 'Sheet 2' => [
21
- {'Column A' => 'Text A', 'Column B' => 'Text B'}
85
+ 'numbers' => [
86
+ {'x' => 1, 'y' => 2},
87
+ {'x' => 3, 'y' => 4},
88
+ {'x' => 5, 'y' => 6}
22
89
  ],
23
- 'Sheet 3' => []
90
+ 'empty' => [],
24
91
  }
25
92
 
26
- ExcelUtils.write filename, data
27
-
28
- workbook = ExcelUtils.read filename
29
-
30
- workbook.to_h.must_equal data
93
+ assert_wrote data
31
94
  end
32
95
 
33
96
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excel_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-03 00:00:00.000000000 Z
11
+ date: 2021-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inflecto
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.7'
33
+ version: '2.8'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.7'
40
+ version: '2.8'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: roo-xls
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.1'
47
+ version: '1.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.1'
54
+ version: '1.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: write_xlsx
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.85'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nesquikcsv
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.1'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -188,15 +202,26 @@ files:
188
202
  - Rakefile
189
203
  - excel_utils.gemspec
190
204
  - lib/excel_utils.rb
191
- - lib/excel_utils/sheet.rb
205
+ - lib/excel_utils/sheets/base.rb
206
+ - lib/excel_utils/sheets/csv.rb
207
+ - lib/excel_utils/sheets/excel.rb
208
+ - lib/excel_utils/sheets/excel_stream.rb
192
209
  - lib/excel_utils/version.rb
193
- - lib/excel_utils/workbook.rb
210
+ - lib/excel_utils/workbooks/csv.rb
211
+ - lib/excel_utils/workbooks/excel.rb
194
212
  - lib/excel_utils/writer.rb
195
213
  - spec/coverage_helper.rb
196
214
  - spec/minitest_helper.rb
197
215
  - spec/read_spec.rb
198
- - spec/sample.xls
199
- - spec/sample.xlsx
216
+ - spec/resources/basic.csv
217
+ - spec/resources/basic.ods
218
+ - spec/resources/basic.xls
219
+ - spec/resources/basic.xlsm
220
+ - spec/resources/basic.xlsx
221
+ - spec/resources/custom_extension.tmp
222
+ - spec/resources/empty.csv
223
+ - spec/resources/multiple.xlsx
224
+ - spec/resources/only_headers.csv
200
225
  - spec/write_spec.rb
201
226
  homepage: https://github.com/gabynaiman/excel_utils
202
227
  licenses:
@@ -225,6 +250,13 @@ test_files:
225
250
  - spec/coverage_helper.rb
226
251
  - spec/minitest_helper.rb
227
252
  - spec/read_spec.rb
228
- - spec/sample.xls
229
- - spec/sample.xlsx
253
+ - spec/resources/basic.csv
254
+ - spec/resources/basic.ods
255
+ - spec/resources/basic.xls
256
+ - spec/resources/basic.xlsm
257
+ - spec/resources/basic.xlsx
258
+ - spec/resources/custom_extension.tmp
259
+ - spec/resources/empty.csv
260
+ - spec/resources/multiple.xlsx
261
+ - spec/resources/only_headers.csv
230
262
  - spec/write_spec.rb
@@ -1,50 +0,0 @@
1
- module ExcelUtils
2
- class Sheet
3
-
4
- include Enumerable
5
-
6
- attr_reader :name, :normalize_column_names
7
-
8
- def initialize(name, spreadsheet, normalize_column_names: false)
9
- @name = name
10
- @spreadsheet = spreadsheet
11
- @normalize_column_names = normalize_column_names
12
- end
13
-
14
- def column_names
15
- @column_names ||= begin
16
- if sheet.first_row
17
- first_row = sheet.row sheet.first_row
18
- normalize_column_names ? first_row.map { |n| Inflecto.underscore(n.strip.gsub(' ', '_')).to_sym } : first_row
19
- else
20
- []
21
- end
22
- end
23
- end
24
-
25
- def each(&block)
26
- rows.each(&block)
27
- end
28
-
29
- private
30
-
31
- attr_reader :spreadsheet
32
-
33
- def sheet
34
- spreadsheet.sheet name
35
- end
36
-
37
- def rows
38
- @rows ||= begin
39
- if sheet.first_row
40
- sheet.to_a[1..-1].map do |row|
41
- Hash[column_names.zip(row)]
42
- end
43
- else
44
- []
45
- end
46
- end
47
- end
48
-
49
- end
50
- end
@@ -1,33 +0,0 @@
1
- module ExcelUtils
2
- class Workbook
3
-
4
- attr_reader :filename, :normalize_column_names
5
-
6
- def initialize(filename, normalize_column_names: false)
7
- @filename = filename
8
- @normalize_column_names = normalize_column_names
9
- @spreadsheet = Roo::Spreadsheet.open filename
10
- end
11
-
12
- def sheets
13
- @sheets ||= spreadsheet.sheets.map do |name|
14
- Sheet.new name, spreadsheet, normalize_column_names: normalize_column_names
15
- end
16
- end
17
-
18
- def [](sheet_name)
19
- sheets.detect { |sheet| sheet.name == sheet_name }
20
- end
21
-
22
- def to_h
23
- sheets.each_with_object({}) do |sheet, hash|
24
- hash[sheet.name] = sheet.to_a
25
- end
26
- end
27
-
28
- private
29
-
30
- attr_reader :spreadsheet
31
-
32
- end
33
- end
data/spec/sample.xlsx DELETED
Binary file