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 +4 -4
- data/excel_utils.gemspec +3 -2
- data/lib/excel_utils.rb +15 -5
- data/lib/excel_utils/sheets/base.rb +41 -0
- data/lib/excel_utils/sheets/csv.rb +28 -0
- data/lib/excel_utils/sheets/excel.rb +35 -0
- data/lib/excel_utils/sheets/excel_stream.rb +35 -0
- data/lib/excel_utils/version.rb +2 -2
- data/lib/excel_utils/workbooks/csv.rb +36 -0
- data/lib/excel_utils/workbooks/excel.rb +41 -0
- data/lib/excel_utils/writer.rb +17 -8
- data/spec/minitest_helper.rb +24 -1
- data/spec/read_spec.rb +113 -66
- data/spec/resources/basic.csv +5 -0
- data/spec/resources/basic.ods +0 -0
- data/spec/{sample.xls → resources/basic.xls} +0 -0
- data/spec/resources/basic.xlsm +0 -0
- data/spec/resources/basic.xlsx +0 -0
- data/spec/resources/custom_extension.tmp +0 -0
- data/spec/resources/empty.csv +0 -0
- data/spec/resources/multiple.xlsx +0 -0
- data/spec/resources/only_headers.csv +1 -0
- data/spec/write_spec.rb +80 -17
- metadata +44 -12
- data/lib/excel_utils/sheet.rb +0 -50
- data/lib/excel_utils/workbook.rb +0 -33
- data/spec/sample.xlsx +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70807b62912a2e82da93e85e0275b6f3f6b6a8f010f70a7924546f31f0d93087
|
4
|
+
data.tar.gz: 6b78f90fd4174174044c9464299bf645bc13619251b1fde6f0ac9079a00b6d14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
23
|
-
spec.add_runtime_dependency 'roo-xls', '~> 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/
|
10
|
-
require_relative 'excel_utils/
|
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
|
-
|
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
|
data/lib/excel_utils/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module ExcelUtils
|
2
|
-
VERSION = '1.
|
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
|
data/lib/excel_utils/writer.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
module ExcelUtils
|
2
2
|
class Writer
|
3
3
|
|
4
|
-
|
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.
|
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
|
-
|
45
|
+
row_index = r + 1
|
46
|
+
header.each_with_index do |column, col_index|
|
42
47
|
if row[column]
|
43
|
-
if row[column].
|
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
|
54
|
+
sheet.write_date_time row_index, col_index, time.to_time.strftime(TIME_FORMAT), formats[type]
|
55
|
+
|
47
56
|
else
|
48
|
-
sheet.write
|
57
|
+
sheet.write row_index, col_index, row[column]
|
49
58
|
end
|
50
59
|
end
|
51
60
|
end
|
data/spec/minitest_helper.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
15
|
+
let(:csv?) { File.extname(filename) == '.csv' }
|
26
16
|
|
27
|
-
|
28
|
-
|
29
|
-
let(:filename) { File.expand_path "../sample.#{extension}", __FILE__ }
|
17
|
+
let(:expected_sheet_name) { csv? ? 'default' : 'Sheet1' }
|
30
18
|
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
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
|
-
|
60
|
+
describe 'Sheet' do
|
50
61
|
|
51
|
-
|
62
|
+
it 'name' do
|
63
|
+
sheet.name.must_equal expected_sheet_name
|
64
|
+
end
|
52
65
|
|
53
|
-
|
66
|
+
it 'normalize_column_names' do
|
67
|
+
sheet.normalize_column_names.must_equal workbook.normalize_column_names
|
68
|
+
end
|
54
69
|
|
55
|
-
|
56
|
-
|
57
|
-
|
70
|
+
it 'column_names' do
|
71
|
+
sheet.column_names.must_equal expected_columns
|
72
|
+
end
|
58
73
|
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
70
89
|
|
71
|
-
|
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
|
-
|
93
|
+
workbook.sheets.map(&:name).must_equal ['default']
|
82
94
|
|
83
|
-
|
95
|
+
workbook['default'].column_names.must_equal []
|
96
|
+
workbook['default'].to_a.must_equal []
|
84
97
|
|
85
|
-
|
98
|
+
workbook.to_h.must_equal 'default' => []
|
99
|
+
end
|
86
100
|
|
87
|
-
|
88
|
-
|
89
|
-
end
|
101
|
+
it 'only_headers.csv' do
|
102
|
+
workbook = ExcelUtils.read resource_path('only_headers.csv')
|
90
103
|
|
91
|
-
|
92
|
-
sheet.to_a.must_equal expected_rows(sheet)
|
93
|
-
end
|
104
|
+
workbook.sheets.map(&:name).must_equal ['default']
|
94
105
|
|
95
|
-
|
106
|
+
workbook['default'].column_names.must_equal ['ID', 'Value']
|
107
|
+
workbook['default'].to_a.must_equal []
|
96
108
|
|
97
|
-
|
109
|
+
workbook.to_h.must_equal 'default' => []
|
110
|
+
end
|
98
111
|
|
99
|
-
|
112
|
+
it 'custom_extension.tmp' do
|
113
|
+
workbook = ExcelUtils.read resource_path('custom_extension.tmp'), extension: 'xlsx'
|
100
114
|
|
101
|
-
|
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
|
Binary file
|
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
|
-
|
5
|
+
def assert_wrote(data, expected=data)
|
6
|
+
filename = tmp_path "#{SecureRandom.uuid}.xlsx"
|
6
7
|
|
7
|
-
|
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 '
|
79
|
+
it 'Multiple sheets' do
|
15
80
|
data = {
|
16
|
-
'
|
17
|
-
{'column_a' =>
|
18
|
-
{'
|
81
|
+
'strings' => [
|
82
|
+
{'column_a' => 'text 1', 'column_b' => 'text 2'},
|
83
|
+
{'column_a' => 'text 3', 'column_b' => 'text 4'}
|
19
84
|
],
|
20
|
-
'
|
21
|
-
{'
|
85
|
+
'numbers' => [
|
86
|
+
{'x' => 1, 'y' => 2},
|
87
|
+
{'x' => 3, 'y' => 4},
|
88
|
+
{'x' => 5, 'y' => 6}
|
22
89
|
],
|
23
|
-
'
|
90
|
+
'empty' => [],
|
24
91
|
}
|
25
92
|
|
26
|
-
|
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.
|
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:
|
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.
|
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.
|
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.
|
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.
|
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/
|
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/
|
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/
|
199
|
-
- spec/
|
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/
|
229
|
-
- spec/
|
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
|
data/lib/excel_utils/sheet.rb
DELETED
@@ -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
|
data/lib/excel_utils/workbook.rb
DELETED
@@ -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
|