def table(obj, maxrows: 15, maxcols: 15, **options)
raise ArgumentError, 'Invalid :maxrows' if maxrows && maxrows < 3
raise ArgumentError, 'Invalid :maxcols' if maxcols && maxcols < 3
return obj unless obj.respond_to?(:each)
rows = []
if obj.respond_to?(:keys)
header = obj.keys
keys = (0...obj.keys.size).to_a
cols = obj.values.map {|x| [x].flatten(1) }
num_rows = cols.map(&:size).max
rows = []
(0...num_rows).each do |i|
rows << []
(0...cols.size).each do |j|
rows[i][j] = cols[j][i]
end
end
else
keys = nil
array_size = 0
obj.each do |row|
if row.respond_to?(:keys)
keys ||= Set.new
keys.merge(row.keys)
elsif row.respond_to?(:map)
array_size = row.size if array_size < row.size
end
rows << row
end
if keys
header = keys.to_a
keys.merge(0...array_size)
else
keys = 0...array_size
end
keys = keys.to_a
end
header ||= keys if options[:header]
rows1, rows2 = rows, nil
keys1, keys2 = keys, nil
header1, header2 = header, nil
if maxcols && keys.size > maxcols
keys1 = keys[0...maxcols / 2]
keys2 = keys[-maxcols / 2 + 1..-1]
if header
header1 = header[0...maxcols / 2]
header2 = header[-maxcols / 2 + 1..-1]
end
end
if maxrows && rows.size > maxrows
rows1 = rows[0...maxrows / 2]
rows2 = rows[-maxrows / 2 + 1..-1]
end
table = +'<table>'
if header1 && options[:header] != false
table << '<tr>' << header1.map {|k| "<th>#{cell k}</th>" }.join
table << "<th>…</th>" << header2.map {|k| "<th>#{cell k}</th>" }.join if keys2
table << '</tr>'
end
row_block(table, rows1, keys1, keys2)
if rows2
table << "<tr><td#{keys1.size > 1 ? " colspan='#{keys1.size}'" : ''}>⋮</td>"
table << "<td>⋱</td><td#{keys2.size > 1 ? " colspan='#{keys2.size}'" : ''}>⋮</td>" if keys2
table << '</tr>'
row_block(table, rows2, keys1, keys2)
end
table << '</table>'
end