class Terminal::Table

Constants

VERSION

Attributes

headings[R]
title[R]

Public Class Methods

new(options = {}) click to toggle source

Generates a ASCII table with the given options.

# File lib/terminal-table/table.rb, line 12
def initialize options = {}, &block
  @headings = []
  @rows = []
  @column_widths = []
  self.style = options.fetch :style, {}
  self.headings = options.fetch :headings, []
  self.rows = options.fetch :rows, []
  self.title = options.fetch :title, nil
  yield_or_eval(&block) if block

  style.on_change(:width) { require_column_widths_recalc }
end

Public Instance Methods

<<(array)
Alias for: add_row
==(other) click to toggle source

Check if other is equal to self. other is considered equal if it contains the same headings and rows.

# File lib/terminal-table/table.rb, line 175
def == other
  if other.respond_to? :render and other.respond_to? :rows
    self.headings == other.headings and self.rows == other.rows
  end
end
add_row(array) click to toggle source

Add a row.

# File lib/terminal-table/table.rb, line 39
def add_row array
  row = array == :separator ? Separator.new(self) : Row.new(self, array)
  @rows << row
  require_column_widths_recalc unless row.is_a?(Separator)
end
Also aliased as: <<
add_separator() click to toggle source

Add a separator.

# File lib/terminal-table/table.rb, line 49
def add_separator
  self << :separator
end
align_column(n, alignment) click to toggle source

Align column n to the given alignment of :center, :left, or :right.

# File lib/terminal-table/table.rb, line 28
def align_column n, alignment
  r = rows
  column(n).each_with_index do |col, i|
    cell = r[i][n]
    cell.alignment = alignment unless cell.alignment?
  end
end
cell_padding() click to toggle source
# File lib/terminal-table/table.rb, line 57
def cell_padding
  style.padding_left + style.padding_right
end
cell_spacing() click to toggle source
# File lib/terminal-table/table.rb, line 53
def cell_spacing
  cell_padding + style.border_y.length
end
column(n, method = :value, array = rows) click to toggle source

Return column n.

# File lib/terminal-table/table.rb, line 64
def column n, method = :value, array = rows
  array.map { |row|
    # for each cells in a row, find the column with index
    # just greater than the required one, and go back one.
    index = col = 0
    row.cells.each do |cell|
      break if index > n
      index += cell.colspan
      col += 1
    end
    cell = row[col - 1]
    cell && method ? cell.__send__(method) : cell
  }.compact
end
column_width(n) click to toggle source

Return length of column n.

# File lib/terminal-table/table.rb, line 96
def column_width n
  column_widths[n] || 0
end
Also aliased as: length_of_column
column_with_headings(n, method = :value) click to toggle source

Return n column including headings.

# File lib/terminal-table/table.rb, line 82
def column_with_headings n, method = :value
  column n, method, headings_with_rows
end
columns() click to toggle source

Return columns.

# File lib/terminal-table/table.rb, line 89
def columns
  (0...number_of_columns).map { |n| column n }
end
headings=(arrays) click to toggle source

Set the headings

# File lib/terminal-table/table.rb, line 111
def headings= arrays
  arrays = [arrays] unless arrays.first.is_a?(Array)
  @headings = arrays.map do |array|
    row = Row.new(self, array)
    require_column_widths_recalc
    row
  end
end
length_of_column(n)
Alias for: column_width
number_of_columns() click to toggle source

Return total number of columns available.

# File lib/terminal-table/table.rb, line 104
def number_of_columns
  headings_with_rows.map { |r| r.number_of_columns }.max || 0
end
render() click to toggle source

Render the table.

# File lib/terminal-table/table.rb, line 123
def render
  separator = Separator.new(self)
  buffer = style.border_top ? [separator] : []
  unless @title.nil?
    buffer << Row.new(self, [title_cell_options])
    buffer << separator
  end
  @headings.each do |row|
    unless row.cells.empty?
      buffer << row
      buffer << separator
    end
  end
  if style.all_separators
    buffer += @rows.product([separator]).flatten
  else
    buffer += @rows
    buffer << separator if style.border_bottom
  end
  buffer.map { |r| style.margin_left + r.render.rstrip }.join("\n")
end
Also aliased as: to_s
rows() click to toggle source

Return rows without separator rows.

# File lib/terminal-table/table.rb, line 149
def rows
  @rows.reject { |row| row.is_a? Separator }
end
rows=(array) click to toggle source
# File lib/terminal-table/table.rb, line 153
def rows= array
  @rows = []
  array.each { |arr| self << arr }
end
style() click to toggle source
# File lib/terminal-table/table.rb, line 162
def style
  @style ||= Style.new
end
style=(options) click to toggle source
# File lib/terminal-table/table.rb, line 158
def style=(options)
  style.apply options
end
title=(title) click to toggle source
# File lib/terminal-table/table.rb, line 166
def title=(title)
  @title = title
  require_column_widths_recalc
end
to_s()
Alias for: render

Private Instance Methods

column_widths() click to toggle source
# File lib/terminal-table/table.rb, line 338
def column_widths
  recalc_column_widths if @require_column_widths_recalc
  @column_widths
end
columns_width() click to toggle source
# File lib/terminal-table/table.rb, line 183
def columns_width
  column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y.length
end
headings_with_rows() click to toggle source

Return headings combined with rows.

# File lib/terminal-table/table.rb, line 317
def headings_with_rows
  @headings + rows
end
recalc_column_widths() click to toggle source
# File lib/terminal-table/table.rb, line 187
def recalc_column_widths
  @require_column_widths_recalc = false
  n_cols = number_of_columns
  space_width = cell_spacing
  return if n_cols == 0

  # prepare rows
  all_rows = headings_with_rows
  all_rows << Row.new(self, [title_cell_options]) unless @title.nil?

  # DP states, dp[colspan][index][split_offset] => column_width.
  dp = []

  # prepare initial value for DP.
  all_rows.each do |row|
    index = 0
    row.cells.each do |cell|
      cell_value = cell.value_for_column_width_recalc
      cell_width = Unicode::DisplayWidth.of(cell_value.to_s)
      colspan = cell.colspan

      # find column width from each single cell.
      dp[colspan] ||= []
      dp[colspan][index] ||= [0]        # add a fake cell with length 0.
      dp[colspan][index][colspan] ||= 0 # initialize column length to 0.

      # the last index `colspan` means width of the single column (split
      # at end of each column), not a width made up of multiple columns.
      single_column_length = [cell_width, dp[colspan][index][colspan]].max
      dp[colspan][index][colspan] = single_column_length

      index += colspan
    end
  end

  # run DP.
  (1..n_cols).each do |colspan|
    dp[colspan] ||= []
    (0..n_cols-colspan).each do |index|
      dp[colspan][index] ||= [1]
      (1...colspan).each do |offset|
        # processed level became reverse map from width => [offset, ...].
        left_colspan = offset
        left_index = index
        left_width = dp[left_colspan][left_index].keys.first

        right_colspan = colspan - left_colspan
        right_index = index + offset
        right_width = dp[right_colspan][right_index].keys.first

        dp[colspan][index][offset] = left_width + right_width + space_width
      end

      # reverse map it for resolution (max width and short offset first).
      rmap = {}
      dp[colspan][index].each_with_index do |width, offset|
        rmap[width] ||= []
        rmap[width] << offset
      end

      # sort reversely and store it back.
      dp[colspan][index] = Hash[rmap.sort.reverse]
    end
  end

  resolve = lambda do |colspan, full_width, index = 0|
    # stop if reaches the bottom level.
    return @column_widths[index] = full_width if colspan == 1

    # choose best split offset for partition, or second best result
    # if first one is not dividable.
    candidate_offsets = dp[colspan][index].collect(&:last).flatten
    offset = candidate_offsets[0]
    offset = candidate_offsets[1] if offset == colspan

    # prepare for next round.
    left_colspan = offset
    left_index = index
    left_width = dp[left_colspan][left_index].keys.first

    right_colspan = colspan - left_colspan
    right_index = index + offset
    right_width = dp[right_colspan][right_index].keys.first

    # calculate reference column width, give remaining spaces to left.
    total_non_space_width = full_width - (colspan - 1) * space_width
    ref_column_width = total_non_space_width / colspan
    remainder = total_non_space_width % colspan
    rem_left_width = [remainder, left_colspan].min
    rem_right_width = remainder - rem_left_width
    ref_left_width = ref_column_width * left_colspan +
                     (left_colspan - 1) * space_width + rem_left_width
    ref_right_width = ref_column_width * right_colspan +
                      (right_colspan - 1) * space_width + rem_right_width

    # at most one width can be greater than the reference width.
    if left_width <= ref_left_width and right_width <= ref_right_width
      # use refernce width (evenly partition).
      left_width = ref_left_width
      right_width = ref_right_width
    else
      # the wider one takes its value, shorter one takes the rest.
      if left_width > ref_left_width
        right_width = full_width - left_width - space_width
      else
        left_width = full_width - right_width - space_width
      end
    end

    # run next round.
    resolve.call(left_colspan, left_width, left_index)
    resolve.call(right_colspan, right_width, right_index)
  end

  full_width = dp[n_cols][0].keys.first
  unless style.width.nil?
    new_width = style.width - space_width - style.border_y.length
    if new_width < full_width
      raise "Table width exceeds wanted width " +
            "of #{style.width} characters."
    end
    full_width = new_width
  end

  resolve.call(n_cols, full_width)
end
require_column_widths_recalc() click to toggle source
# File lib/terminal-table/table.rb, line 334
def require_column_widths_recalc
  @require_column_widths_recalc = true
end
title_cell_options() click to toggle source
# File lib/terminal-table/table.rb, line 330
def title_cell_options
  {:value => @title, :alignment => :center, :colspan => number_of_columns}
end
yield_or_eval() { |self| ... } click to toggle source
# File lib/terminal-table/table.rb, line 321
def yield_or_eval &block
  return unless block
  if block.arity > 0
    yield self
  else
    self.instance_eval(&block)
  end
end