Skip to content

Commit

Permalink
Add more output preference options to CSV & XLSX writers.
Browse files Browse the repository at this point in the history
  • Loading branch information
blambeau committed Jan 18, 2024
1 parent ca7eb05 commit 811d38a
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 53 deletions.
4 changes: 3 additions & 1 deletion lib/bmg/reader/csv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def to_s
private

def tuple(row)
row.to_hash.each_with_object({}){|(k,v),h| h[k.to_sym] = v }
row.to_hash.each_with_object({}){|(k,v),h|
h[k.to_sym] = v
}
end

def handle_type(type)
Expand Down
12 changes: 11 additions & 1 deletion lib/bmg/support/ordering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ module Bmg
class Ordering

def initialize(attrs)
@attrs = attrs
@attrs = if attrs.empty?
[]
elsif attrs.first.is_a?(Symbol)
attrs.map{|a| [a, :asc] }
else
attrs
end
end
attr_reader :attrs

Expand Down Expand Up @@ -33,5 +39,9 @@ def compare_attrs(t1, t2)
0
end

def to_a
attrs.to_a
end

end # class Ordering
end # module Bmg
28 changes: 27 additions & 1 deletion lib/bmg/support/output_preferences.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class OutputPreferences

DEFAULT_PREFS = {
attributes_ordering: nil,
tuple_ordering: nil,
extra_attributes: :after
}

Expand All @@ -17,6 +18,12 @@ def self.dress(arg)
new(arg)
end

def tuple_ordering
return nil unless to = options[:tuple_ordering]

@tuple_ordering = Ordering.new(to)
end

def attributes_ordering
options[:attributes_ordering]
end
Expand All @@ -25,10 +32,29 @@ def extra_attributes
options[:extra_attributes]
end

def grouping_attributes
options[:grouping_attributes]
end

def erase_redundance_in_group(before, current)
return [nil, current] unless ga = grouping_attributes
return [current, current] unless before

new_before, new_current = current.dup, current.dup
ga.each do |attr|
return [new_before, new_current] unless before[attr] == current[attr]
new_current[attr] = nil
end
[new_before, new_current]
end

def order_attrlist(attrlist)
return attrlist if attributes_ordering.nil?

index = Hash[attributes_ordering.each_with_index.to_a]
attrlist.sort{|a,b|
base = attrlist
base = attrlist & attributes_ordering if extra_attributes == :ignored
base.sort{|a,b|
ai, bi = index[a], index[b]
if ai && bi
ai <=> bi
Expand Down
11 changes: 11 additions & 0 deletions lib/bmg/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ def infer_headers(from)
attrlist ? output_preferences.order_attrlist(attrlist) : nil
end

def each_tuple(relation, &bl)
if ordering = output_preferences.tuple_ordering
relation
.to_a
.sort{|t1,t2| ordering.compare_attrs(t1, t2) }
.each_with_index(&bl)
else
relation.each_with_index(&bl)
end
end

end # module Writer
end # module Bmg
require_relative 'writer/csv'
4 changes: 3 additions & 1 deletion lib/bmg/writer/csv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ def call(relation, string_or_io = nil)
require 'csv'
string_or_io, to_s = string_or_io.nil? ? [StringIO.new, true] : [string_or_io, false]
headers, csv = infer_headers(relation.type), nil
relation.each do |tuple|
previous = nil
each_tuple(relation) do |tuple,i|
if csv.nil?
headers = infer_headers(tuple) if headers.nil?
csv_opts = csv_options.merge(headers: headers)
csv = CSV.new(string_or_io, **csv_opts)
end
previous, tuple = output_preferences.erase_redundance_in_group(previous, tuple)
csv << headers.map{|h| tuple[h] }
end
to_s ? string_or_io.string : string_or_io
Expand Down
16 changes: 9 additions & 7 deletions lib/bmg/writer/xlsx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ class Xlsx
DEFAULT_OPTIONS = {
}

def initialize(csv_options, output_preferences = nil)
@csv_options = DEFAULT_OPTIONS.merge(csv_options)
def initialize(xlsx_options, output_preferences = nil)
@xlsx_options = DEFAULT_OPTIONS.merge(xlsx_options)
@output_preferences = OutputPreferences.dress(output_preferences)
end
attr_reader :csv_options, :output_preferences
attr_reader :xlsx_options, :output_preferences

def call(relation, path)
require 'write_xlsx'
Expand All @@ -21,22 +21,24 @@ def call(relation, path)
attr_reader :workbook, :worksheet

def _call(relation, path)
@workbook = WriteXLSX.new(path)
@worksheet = workbook.add_worksheet
@workbook = xlsx_options[:workbook] || WriteXLSX.new(path)
@worksheet = xlsx_options[:worksheet] || workbook.add_worksheet

headers = infer_headers(relation.type)
relation.each_with_index do |tuple,i|
before = nil
each_tuple(relation) do |tuple,i|
headers = infer_headers(tuple) if headers.nil?
headers.each_with_index do |h,i|
worksheet.write_string(0, i, h)
end if i == 0
before, tuple = output_preferences.erase_redundance_in_group(before, tuple)
headers.each_with_index do |h,j|
meth, *args = write_pair(tuple[h])
worksheet.send(meth, 1+i, j, *args)
end
end

workbook.close
workbook.close unless xlsx_options[:workbook]
path
end

Expand Down
12 changes: 12 additions & 0 deletions spec/unit/support/test_ordering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ module Bmg
Ordering.new(attrs)
}

describe 'new' do
it 'supports pairs' do
o = Ordering.new([[:name, :desc],[:id, :asc]])
expect(o.to_a).to eql([[:name, :desc],[:id, :asc]])
end

it 'supports attribute names' do
o = Ordering.new([:name, :id])
expect(o.to_a).to eql([[:name, :asc],[:id, :asc]])
end
end

context 'when a single attr, asc' do
let(:attrs) {
[[:title, :asc]]
Expand Down
Loading

0 comments on commit 811d38a

Please sign in to comment.