excel_utils 1.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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