class Columnize::Columnizer

def arrange_by_column(list, nrows, ncols)

[[1,3,5], [2,4]]
arrange_by_column((1..5).to_a, 2, 3) =>
Here is an example:

information.
access this for the list data or for the width
In either horizontal or vertical arrangement, we will need to

array into a 2-dimensional lists of lists organized by columns.
Given +list+, +ncols+, +nrows+, arrange the one-dimensional
def arrange_by_column(list, nrows, ncols)
  (0...ncols).map do |i|
    (0..nrows-1).inject([]) do |row, j|
      k = i + (j * ncols)
      k < list.length ? row << list[k] : row
    end
  end
end

def arrange_by_row(list, nrows, ncols)

[[1,2], [3,4], [5]],
arrange_by_row((1..5).to_a, 3, 2) =>
Here is an example:

information.
access this for the list data or for the width
In either horizontal or vertical arrangement, we will need to

array into a 2-dimensional lists of lists organized by rows.
Given +list+, +ncols+, +nrows+, arrange the one-dimensional
def arrange_by_row(list, nrows, ncols)
  (0...nrows).map {|r| list[r*ncols, ncols] }.compact
end

def columnize

def columnize
  return @short_circuit if @short_circuit
  rows, colwidths = min_rows_and_colwidths
  ncols = colwidths.length
  justify = lambda {|t, c|
      @ljust ? t.ljust(colwidths[c]) : t.rjust(colwidths[c])
  }
  textify = lambda do |row|
    row.map!.with_index(&justify) unless ncols == 1 && @ljust
    "#{@line_prefix}#{row.join(@colsep)}#{@line_suffix}"
  end
  text = rows.map(&textify)
  text.first.sub!(/^#{@line_prefix}/, @array_prefix) unless @array_prefix.empty?
  text.last.sub!(/#{@line_suffix}$/, @array_suffix) unless @array_suffix.empty?
  text.join("\n") # + "\n" # if we want extra separation
end

def initialize(list=[], opts={})

def initialize(list=[], opts={})
  self.list = list
  self.opts = DEFAULT_OPTS.merge(opts)
end

def list=(list)

def list=(list)
  @list = list
  if @list.is_a? Array
    @short_circuit = @list.empty? ? "<empty>\n" : nil
  else
    @short_circuit = ''
    @list = []
  end
end

def min_rows_and_colwidths

compute the smallest number of rows and the max widths for each column
TODO: make this a method, rather than a function (?)
def min_rows_and_colwidths
  list = @list.map(&@stringify)
  cell_widths = list.map(&@term_adjuster).map(&:size)
  # Set default arrangement: one atom per row
  cell_width_max = cell_widths.max
  result = [arrange_by_row(list, list.size, 1), [cell_width_max]]
  # If any atom > @displaywidth, stop and use one atom per row.
  return result if cell_width_max > @displaywidth
  # For horizontal arrangement, we want to *maximize* the number
  # of columns. Thus the candidate number of rows (+sizes+) starts
  # at the minumum number of rows, 1, and increases.
  # For vertical arrangement, we want to *minimize* the number of
  # rows. So here the candidate number of columns (+sizes+) starts
  # at the maximum number of columns, list.length, and
  # decreases. Also the roles of columns and rows are reversed
  # from horizontal arrangement.
  # Loop from most compact arrangement to least compact, stopping
  # at the first successful packing.  The below code is tricky,
  # but very cool.
  #
  # FIXME: In the below code could be DRY'd. (The duplication got
  # introduced when I revised the code - rocky)
  if @arrange_vertical
    (1..list.length).each do |size|
      other_size = (list.size + size - 1) / size
      colwidths = arrange_by_row(cell_widths, other_size, size).map(&:max)
      totwidth = colwidths.inject(&:+) + ((colwidths.length-1) * @colsep.length)
      return [arrange_by_column(list, other_size, size), colwidths] if
        totwidth <= @displaywidth
    end
  else
    list.length.downto(1).each do |size|
      other_size = (list.size + size - 1) / size
      colwidths = arrange_by_column(cell_widths, other_size, size).map(&:max)
      totwidth = colwidths.inject(&:+) + ((colwidths.length-1) * @colsep.length)
      return [arrange_by_row(list, other_size, size), colwidths] if
        totwidth <= @displaywidth
    end
  end
  result
end

def opts=(opts)

TODO: freeze @opts
def opts=(opts)
  @opts = opts
  OLD_AND_NEW_KEYS.each {|old, new| @opts[new] = @opts.delete(old) if @opts.keys.include?(old) and !@opts.keys.include?(new) }
  @opts.merge!(ARRANGE_ARRAY_OPTS) if @opts[:arrange_array]
  set_attrs_from_opts
end

def set_attrs_from_opts

def set_attrs_from_opts
  ATTRS.each {|attr| self.instance_variable_set "@#{attr}", @opts[attr] }
  @ljust = !@list.all? {|datum| datum.kind_of?(Numeric)} if @ljust == :auto
  @displaywidth -= @line_prefix.length
  @displaywidth = @line_prefix.length + 4 if @displaywidth < 4
  @stringify = @colfmt ? lambda {|li| @colfmt % li } : lambda {|li| li.to_s }
  @term_adjuster = @opts[:term_adjust] ? lambda {|c| c.gsub(/\e\[.*?m/, '') } : lambda {|c| c }
end

def update_opts(opts)

def update_opts(opts)
  self.opts = @opts.merge(opts)
end