14 changed files with 344 additions and 48 deletions
@ -0,0 +1,41 @@ |
|||||||
|
require_relative '../args/input_file' |
||||||
|
require_relative '../args/import' |
||||||
|
require_relative '../import/xlsx_importer' |
||||||
|
require_relative '../errors_warnings/warn_unexpected_params' |
||||||
|
|
||||||
|
module Squib |
||||||
|
# DSL method. See http://squib.readthedocs.io |
||||||
|
def xlsx(opts = {}, &block) |
||||||
|
DSL::Xlsx.new(__callee__).run(opts, &block) |
||||||
|
end |
||||||
|
module_function :xlsx |
||||||
|
|
||||||
|
class Deck |
||||||
|
# DSL method. See http://squib.readthedocs.io |
||||||
|
def xlsx(opts = {}, &block) |
||||||
|
DSL::Xlsx.new(__callee__).run(opts, &block) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
module DSL |
||||||
|
class Xlsx |
||||||
|
include WarnUnexpectedParams |
||||||
|
attr_reader :dsl_method, :block |
||||||
|
|
||||||
|
def initialize(dsl_method) |
||||||
|
@dsl_method = dsl_method |
||||||
|
end |
||||||
|
|
||||||
|
def self.accepted_params |
||||||
|
%i( file sheet strip explode ) |
||||||
|
end |
||||||
|
|
||||||
|
def run(opts,&block) |
||||||
|
warn_if_unexpected opts |
||||||
|
import_args = Args.extract_import opts |
||||||
|
importer = Squib::Import::XlsxImporter.new |
||||||
|
importer.import_to_dataframe(import_args, &block) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
module Squib |
||||||
|
module Import |
||||||
|
module QuantityExploder |
||||||
|
def explode_quantities(data, qty) |
||||||
|
return data unless data.col? qty.to_s.strip |
||||||
|
qtys = data[qty] |
||||||
|
new_data = Squib::DataFrame.new |
||||||
|
data.each do |col, arr| |
||||||
|
new_data[col] = [] |
||||||
|
qtys.each_with_index do |qty, index| |
||||||
|
qty.to_i.times { new_data[col] << arr[index] } |
||||||
|
end |
||||||
|
end |
||||||
|
return new_data |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
require_relative 'quantity_exploder' |
||||||
|
|
||||||
|
module Squib::Import |
||||||
|
class XlsxImporter |
||||||
|
include Squib::Import::QuantityExploder |
||||||
|
def import_to_dataframe(import, &block) |
||||||
|
s = Roo::Excelx.new(import.file) |
||||||
|
s.default_sheet = s.sheets[import.sheet] |
||||||
|
data = Squib::DataFrame.new |
||||||
|
s.first_column.upto(s.last_column) do |col| |
||||||
|
header = s.cell(s.first_row, col).to_s |
||||||
|
header.strip! if import.strip? |
||||||
|
data[header] = [] |
||||||
|
(s.first_row + 1).upto(s.last_row) do |row| |
||||||
|
cell = s.cell(row, col) |
||||||
|
# Roo hack for avoiding unnecessary .0's on whole integers (https://github.com/roo-rb/roo/issues/139) |
||||||
|
cell = s.excelx_value(row, col) if s.excelx_type(row, col) == [:numeric_or_formula, 'General'] |
||||||
|
cell.strip! if cell.respond_to?(:strip) && import.strip? |
||||||
|
cell = block.yield(header, cell) unless block.nil? |
||||||
|
data[header] << cell |
||||||
|
end# row |
||||||
|
end# col |
||||||
|
explode_quantities(data, import.explode) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
@ -0,0 +1,109 @@ |
|||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe Squib::Deck do |
||||||
|
context '#csv' do |
||||||
|
it 'loads basic csv data' do |
||||||
|
expect(Squib.csv(file: csv_file('basic.csv')).to_h.to_h).to eq({ |
||||||
|
'h1' => [1, 3], |
||||||
|
'h2' => [2, 4] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'collapses duplicate columns and warns' do |
||||||
|
expect(Squib.logger).to receive(:warn) |
||||||
|
.with('CSV duplicated the following column keys: h1,h1') |
||||||
|
expect(Squib.csv(file: csv_file('dup_cols.csv')).to_h.to_h).to eq({ |
||||||
|
'h1' => [1, 3], |
||||||
|
'h2' => [5, 7], |
||||||
|
'H2' => [6, 8], |
||||||
|
'h3' => [9, 10], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'strips spaces by default' do |
||||||
|
expect(Squib.csv(file: csv_file('with_spaces.csv')).to_h).to eq({ |
||||||
|
'With Spaces' => ['a b c', 3], |
||||||
|
'h2' => [2, 4], |
||||||
|
'h3' => [3, nil] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'skips space stripping if told to' do |
||||||
|
expect(Squib.csv(strip: false, file: csv_file('with_spaces.csv')).to_h).to eq({ |
||||||
|
' With Spaces ' => ['a b c ', 3], |
||||||
|
'h2' => [2, 4], |
||||||
|
'h3' => [3, nil] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'explodes quantities' do |
||||||
|
expect(Squib.csv(file: csv_file('qty.csv')).to_h).to eq({ |
||||||
|
'Name' => %w(Ha Ha Ha Ho), |
||||||
|
'qty' => [3, 3, 3, 1], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'explodes quantities on specified header' do |
||||||
|
expect(Squib.csv(explode: 'Quantity', file: csv_file('qty_named.csv')).to_h).to eq({ |
||||||
|
'Name' => %w(Ha Ha Ha Ho), |
||||||
|
'Quantity' => [3, 3, 3, 1], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'loads inline data' do |
||||||
|
hash = Squib.csv(data: "h1,h2\n1,2\n3,4") |
||||||
|
expect(hash.to_h).to eq({ |
||||||
|
'h1' => [1, 3], |
||||||
|
'h2' => [2, 4] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'loads csv with newlines' do |
||||||
|
hash = Squib.csv(file: csv_file('newline.csv')) |
||||||
|
expect(hash.to_h).to eq({ |
||||||
|
'title' => ['Foo'], |
||||||
|
'level' => [1], |
||||||
|
'notes' => ["a\nb"] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'loads custom CSV options' do |
||||||
|
hash = Squib.csv(file: csv_file('custom_opts.csv'), |
||||||
|
col_sep: '-', quote_char: '|') |
||||||
|
expect(hash.to_h).to eq({ |
||||||
|
'x' => ['p'], |
||||||
|
'y' => ['q-r'] |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'yields to block when given' do |
||||||
|
data = Squib.csv(file: csv_file('basic.csv')) do |header, value| |
||||||
|
case header |
||||||
|
when 'h1' |
||||||
|
value * 2 |
||||||
|
else |
||||||
|
'ha' |
||||||
|
end |
||||||
|
end |
||||||
|
expect(data.to_h).to eq({ |
||||||
|
'h1' => [2, 6], |
||||||
|
'h2' => %w(ha ha), |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'replaces newlines whenever its a string' do |
||||||
|
data = Squib.csv(file: csv_file('yield.csv')) do |header, value| |
||||||
|
if value.respond_to? :gsub |
||||||
|
value.gsub '%n', "\n" |
||||||
|
else |
||||||
|
value |
||||||
|
end |
||||||
|
end |
||||||
|
expect(data.to_h).to eq({ |
||||||
|
'a' => ["foo\nbar", 1], |
||||||
|
'b' => [1, "blah\n"], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe Squib::Deck do |
||||||
|
context '#xlsx' do |
||||||
|
it 'loads basic xlsx data' do |
||||||
|
expect(Squib.xlsx(file: xlsx_file('basic.xlsx')).to_h).to eq({ |
||||||
|
'Name' => %w(Larry Curly Mo), |
||||||
|
'General Number' => %w(1 2 3), # general types always get loaded as strings with no conversion |
||||||
|
'Actual Number' => [4.0, 5.0, 6.0], # numbers get auto-converted to integers |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'loads xlsx with formulas' do |
||||||
|
expect(Squib.xlsx(file: xlsx_file('formulas.xlsx')).to_h).to eq({ |
||||||
|
'A' => %w(1 2), |
||||||
|
'B' => %w(3 4), |
||||||
|
'Sum' => %w(4 6), |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'loads xlsm files with macros' do |
||||||
|
expect(Squib.xlsx(file: xlsx_file('with_macros.xlsm')).to_h).to eq({ |
||||||
|
'foo' => %w(8 10), |
||||||
|
'bar' => %w(9 11), |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'strips whitespace by default' do |
||||||
|
expect(Squib.xlsx(file: xlsx_file('whitespace.xlsx')).to_h).to eq({ |
||||||
|
'With Whitespace' => ['foo', 'bar', 'baz'], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'does not strip whitespace when specified' do |
||||||
|
expect(Squib.xlsx(file: xlsx_file('whitespace.xlsx'), strip: false).to_h).to eq({ |
||||||
|
' With Whitespace ' => ['foo ', ' bar', ' baz '], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'yields to block when given' do |
||||||
|
data = Squib.xlsx(file: xlsx_file('basic.xlsx')) do |header, value| |
||||||
|
case header |
||||||
|
when 'Name' |
||||||
|
'he' |
||||||
|
when 'Actual Number' |
||||||
|
value * 2 |
||||||
|
else |
||||||
|
'ha' |
||||||
|
end |
||||||
|
end |
||||||
|
expect(data.to_h).to eq({ |
||||||
|
'Name' => %w(he he he), |
||||||
|
'General Number' => %w(ha ha ha), |
||||||
|
'Actual Number' => [8.0, 10.0, 12.0], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'explodes quantities' do |
||||||
|
expect(Squib.xlsx(explode: 'Quantity', file: xlsx_file('explode_quantities.xlsx')).to_h).to eq({ |
||||||
|
'Name' => ['Zergling', 'Zergling', 'Zergling', 'High Templar'], |
||||||
|
'Quantity' => %w(3 3 3 1), |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe Squib::Deck do |
||||||
|
context '#yaml' do |
||||||
|
it 'loads basic data' do |
||||||
|
expect(Squib.yaml(file: yaml_file('basic.yml')).to_h).to eq({ |
||||||
|
'Name' => %w(Larry Curly Mo), |
||||||
|
'Number' => [4.0, 5.0, 6.0], # numbers get auto-converted to integers |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'explodes quantities' do |
||||||
|
expect(Squib.yaml(explode: 'qty', file: yaml_file('qty.yml')).to_h).to eq({ |
||||||
|
'name' => %w(ha ha he), |
||||||
|
'qty' => [2, 2, 1], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'handles silence' do |
||||||
|
expect(Squib.yaml(file: yaml_file('nilly.yml')).to_h).to eq({ |
||||||
|
'name' => %w(foo bar), |
||||||
|
'desc' => [nil, 'Hello'], |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
it 'yields to block when given' do |
||||||
|
data = Squib.yaml(file: yaml_file('basic.yml')) do |header, value| |
||||||
|
case header |
||||||
|
when 'Name' |
||||||
|
'he' |
||||||
|
when 'Number' |
||||||
|
value * 2 |
||||||
|
else |
||||||
|
'ha' |
||||||
|
end |
||||||
|
end |
||||||
|
expect(data.to_h).to eq({ |
||||||
|
'Name' => %w(he he he), |
||||||
|
'Number' => [8.0, 10.0, 12.0], |
||||||
|
}) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
Loading…
Reference in new issue