class TableGen

TableGen is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Constants

@api private

Line

@api private

VERSION

Attributes

border[RW]

@attribute border [rw] The column separator.

May be of any length. Defaults to a space.

@return [String]
width[W]

Public Class Methods

new() click to toggle source
# File lib/tablegen.rb, line 32
def initialize
  @border = "\x20"
  @columns = []
  @lines = []
  @collapsed = []
end

Public Instance Methods

clear() click to toggle source

Empty the table. Columns settings are conserved.

@see clear!

# File lib/tablegen.rb, line 137
def clear
  @lines.clear
end
clear!() click to toggle source

Empty the table AND delete the columns.

@see clear

# File lib/tablegen.rb, line 144
def clear!
  clear
  @columns.clear
end
column(index) { |col| ... } click to toggle source

Yields and returns the column at the specified index.

@example Change the alignment of the first column

table.column 0 do |col|
  col.alignment = :right
end

@param index [Fixnum] @yield [Column column] the requested column @return [Column] the requested column

@see columns @see Column

# File lib/tablegen.rb, line 52
def column(index)
  unless col = @columns[index]
    if @columns.count < index
      # create columns so the holes are not filled with nil
      (@columns.count..index).each {|i| column i }
    end

    col = Column.new index
    @columns[index] = col
  end

  yield col if block_given?
  col
end
columns(*indexes, &block) click to toggle source

Shorthand to {#column}: Yields specified columns.

@example Allow columns 6 and 8 to be collapsed

table.columns 6, 8 do |col|
  col.collapse = true
end

@param [Array<Fixnum>] indexes @yield [Column column] a column from the index list

@see column @see Column

# File lib/tablegen.rb, line 79
def columns(*indexes, &block)
  indexes.each {|index|
    column index, &block
  }
end
header(*fields) click to toggle source

Add a header row to the table. The fields are not formatted.

@example

table.header 'Product Name', 'Quantity', 'Price'

@param [Array<String>] fields @raise [ArgumentError] at least one field is required

@see row

# File lib/tablegen.rb, line 115
def header(*fields)
  row *fields.map {|name| Header.new name }
end
height() click to toggle source

The minimum height (in lines) of the table. @note Does not calculate wrapped text lines. If required, use {#real_height} instead.

@!attribute [r] height @return [Fixnum]

@see real_height

# File lib/tablegen.rb, line 188
def height
  @lines.count
end
real_height() click to toggle source

Calculates the exact height (in lines) of the table.

@return [Fixnum]

@see height

# File lib/tablegen.rb, line 197
def real_height
  to_s.lines.count
end
real_width() click to toggle source

Calculates the exact width (in characters) of the entire table.

@return [Fixnum]

@see width

# File lib/tablegen.rb, line 177
def real_width
  to_s.each_line.map {|l| real_length l.chomp }.max || 0
end
row(*fields) click to toggle source

Add a row to the table. The fields are formatted with {Column#format}.

@example

table.column 2 do |col|
  col.format = proc {|price|
    "$%.2f" % price
  }
end

# Product Name, Quantity, Price
table.row 'Table Generator', 42, 0

@param [Array<Object>] fields @raise [ArgumentError] at least one field is required

@see header

# File lib/tablegen.rb, line 101
def row(*fields)
  raise ArgumentError, 'wrong number of arguments (0 for 1+)' if fields.empty?
  @lines << Line.new(:row, fields)
end
separator(char = '=') click to toggle source

Add a separator to the table.

@param [String] char the character to repeat

# File lib/tablegen.rb, line 122
def separator(char = '=')
  @lines << Line.new(:separator, char)
end
text(line) click to toggle source

Add a text line to the table. The text is wrapped automatically to fit into the table.

@param line [String]

# File lib/tablegen.rb, line 130
def text(line)
  @lines << Line.new(:text, line)
end
to_s() click to toggle source

Generate the table.

begin
  puts table
rescue TableGen::WidthError
  puts 'Terminal is too small'
end

@return [String] the table @raise [WidthError] if the table is too large to fit in the {#width} constraint @raise [Error] if something is wrong (eg. invalid column alignment)

# File lib/tablegen.rb, line 212
def to_s
  create_columns

  table = ''
  @collapsed.clear

  loop do
    table, missing_width = generate_table
    break if missing_width == 0

    candidates = []
    @columns.each_with_index {|col, index|
      if col.collapse && !@collapsed.include?(index)
        candidates << index
      end
    }

    if candidates.empty?
      raise WidthError, "insufficient width to generate the table"
    end

    @collapsed << candidates.min_by {|index|
      (column_width(index, false) - missing_width).abs
    }
  end

  table
end
width() click to toggle source

The maximum width (in characters) of the table. If unspecified (nil), returns the space required to display every row and header.

@example Fit the table in the terminal

if STDOUT.tty?
  table.width = STDOUT.winsize[1]
end

@return [Fixnum]

@see real_width

# File lib/tablegen.rb, line 160
def width
  return @width unless @width.nil?

  width = 0
  rows.each {|row|
    format = format_row row.data
    length = real_length format
    width = [width, length].max
  }
  width
end

Private Instance Methods

column_width(index, can_stretch = true) click to toggle source
# File lib/tablegen.rb, line 289
def column_width(index, can_stretch = true)
  col = column index

  stretch_index = @columns.find_index {|c|
    c.stretch && !@collapsed.include?(@columns.index(c))
  }

  if can_stretch && index == stretch_index && @width
    used_width = 0
    @columns.each_with_index {|dist_col, dist_index|
      next if dist_index == stretch_index || @collapsed.include?(dist_index)

      dist_width = column_width(dist_index, false)
      next if dist_width.nil?

      used_width += dist_width
      used_width += real_length @border
    }

    remaining_width = @width - used_width
    needed_width = column_width index, false
    [remaining_width, needed_width].max
  else
    sizes = []
    rows.each {|row|
      data = row.data[index]
      next unless data

      length = real_length format_field(col, data, col.min_width)
      sizes << [col.min_width, length].max
    }
    sizes.max
  end
end
create_columns() click to toggle source
# File lib/tablegen.rb, line 332
def create_columns
  rows.each {|row|
    # creates all columns up to the specified index
    column row.data.count - 1
  }
end
format_field(column, data, width) click to toggle source
# File lib/tablegen.rb, line 324
def format_field(column, data, width)
  if data.is_a? Header
    data.name
  else
    column.format[data, width]
  end
end
format_row(fields) click to toggle source
# File lib/tablegen.rb, line 251
def format_row(fields)
  out = ''

  fields.each_with_index {|data, index|
    next if @collapsed.include? index
    col = column index

    width = column_width index
    field = format_field(col, data, width)
    length = real_length field

    pad_width = width - length
    padding = col.padding[0] * pad_width

    alignment = if data.is_a?(Header) && col.header_alignment != :auto
      col.header_alignment
    else
      col.alignment
    end

    out += @border unless out.empty?
    out += \
    case alignment
    when :left
      field + padding
    when :right
      padding + field
    when :center
      left = pad_width / 2
      right = left + (pad_width % 2)
      padding[0...left] + field + padding[0...right]
    else
      raise Error, "invalid alignment '%s'" % col.alignment
    end
  }
  out
end
generate_table() click to toggle source
# File lib/tablegen.rb, line 339
def generate_table
  table = ''
  missing_width = [0]

  @lines.each {|line|
    out = case line.type
    when :row
      format_row line.data
    when :separator
      line.data[0] * width
    when :text
      if width > 0
        line.data.scan(/.{1,#{width}}/).join $/
      else
        line.data
      end
    end
    out.rstrip!

    if line.type == :row
      line_length = real_length out
      missing_width << line_length - @width if @width
    end

    table += out + $/
  }

  return table.chomp, missing_width.max
end
real_length(string) click to toggle source
# File lib/tablegen.rb, line 246
def real_length(string)
  doublesize = string.scan(/\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}/).count
  string.length + doublesize
end
rows() click to toggle source
# File lib/tablegen.rb, line 242
def rows
  @lines.select {|l| l.type == :row }
end