class Tabmani::Table

Attributes

indent[R]
matrix[R]
titles[R]

Public Class Methods

dump_column_format(matrix:, hlines: [], io: $stdout, indent: 0, titles: nil, just: :left, separator: ' ') click to toggle source
# File lib/tabmani/table.rb, line 74
def self.dump_column_format(matrix:,
                            hlines: [],
                            io: $stdout,
                            indent: 0,
                            titles: nil,
                            just: :left,
                            separator: ' ')
  matrix = [titles, * matrix] if titles
  maxima =  self.max_lengths(matrix)
  lines = matrix.map do |row|
    self.line_str(items: row,
                  lengths: maxima,
                  separator: separator,
                  just: just,
                  indent: indent)
  end
  hlines.sort.reverse.each { |index| lines.insert(index, '-' * whole_length(matrix)) }
  io.puts lines.join("\n")
end
get_ranges(ary) click to toggle source

true の範囲を示す二重配列を返す。 各要素は 始点..終点 の各インデックスで出来た範囲。

各要素は[始点, 終点] の各インデックス。
# File lib/tabmani/table.rb, line 101
def self.get_ranges(ary)
  results = []
  start = nil
  prev = false
  ary << false # for true in final item
  ary.each_with_index do |cur, i|
    if prev == false && cur == true
      start = i
      prev = cur
    elsif prev == true && cur == false
      results << (start..(i - 1))
      prev = cur
    else
      next
    end
  end
  ary.pop
  results
end
line_str(items: , lengths: , lineend: nil, just: :left, indent: 0, separator: ' ') click to toggle source
# File lib/tabmani/table.rb, line 165
def self.line_str(items: ,
                  lengths: ,
                  lineend: nil,
                  just: :left,
                  indent: 0,
                  separator: ' ')

  new_items = []
  lengths.each_with_index do |length, index|
    item = items[index].to_s
    new_items[index] = self.padding(str: item, width: lengths[index], place: just)
  end
  str = " " * indent
  str += new_items.join(separator)
  if lineend
    str += lineend
  else
    str.sub!(/ +$/, "")
  end
  str
end
max_lengths(matrix) click to toggle source

return array of max size of item at index

# File lib/tabmani/table.rb, line 188
def self.max_lengths(matrix)
  results = []
  matrix.each do |row|
    row.each_with_index do |item, index|
      item = item.to_s
      results[index] ||= 0
      size = Tabmani::Table.print_size(item)
      results[index] = size if results[index] < size
    end
  end
  results
end
new(matrix: , indent: 0) click to toggle source
# File lib/tabmani/table.rb, line 17
def initialize(matrix: , indent: 0)
  @matrix = matrix
  @titles = nil
  @indent = indent
  @hlines = []
end
num_columns(matrix) click to toggle source
# File lib/tabmani/table.rb, line 161
def self.num_columns(matrix)
  matrix.map{|items| items.size}.max
end
padding(str: , width: , padding: ' ', place: :left) click to toggle source
# File lib/tabmani/table.rb, line 141
def self.padding(str: , width: , padding: ' ', place: :left)
  output_width = str.each_char.map{|c| c.bytesize == 1 ? 1 : 2}.reduce(0, &:+)
  padding_size = [0, width - output_width].max

  case place
  when :left   ; left = 0                ; right = padding_size
  when :right  ; left = padding_size     ; right = 0
  when :center ; left = padding_size / 2 ; right = padding_size - left
  else
    raise SymbolMismatchError, place
  end
  (padding * left) + str + (padding * right)
end
parse(io: , style: , separator: ',') click to toggle source
Wrapper for various parse method.

style: :csv, :blank, or :column separator: option for separator style

# File lib/tabmani/table.rb, line 27
def self.parse(io: , style: , separator: ',')
  case style
  when :csv    ; self.parse_csv(io)
  when :blank  ; self.parse_blanks_separated_value(io)
  when :column ; self.parse_column_based_value(io)
  when :separator ; self.parse_separator(io, separator)
  else;
    raise ParseError, "Unknown style: #{style}"
  end
end
parse_blanks_separated_value(io) click to toggle source

blanks_separated_value

# File lib/tabmani/table.rb, line 51
def self.parse_blanks_separated_value(io)
  lines = io.readlines
  indent = lines.map{|line| /^(\s*)/ =~ line; $1.length}.min
  matrix = lines.map { |line| line.strip.split(/\s+/) }

  lines.map { |line| line.strip.split(/\s+/) }

  self.new(matrix: matrix, indent: indent)
end
parse_column_based_value(io) click to toggle source
column_based_value

縦に貫通する空白列を区切りにする。 各要素の空白は strip する。

# File lib/tabmani/table.rb, line 64
def self.parse_column_based_value(io)
  lines = io.readlines
  return if lines.empty?
  lines.map!      { |line| line.chomp  }
  lines.delete_if { |line| line.empty? } # delete line consist of linefeed only.
  ranges = self.get_ranges(self.projection_ary(lines))
  matrix = lines.map { |line| ranges.map { |range| line[range].to_s.strip} }
  self.new(matrix: matrix)
end
parse_csv(io) click to toggle source

CSV

# File lib/tabmani/table.rb, line 39
def self.parse_csv(io)
  self.new(matrix: CSV.parse(io))
end
parse_separator(io, separator) click to toggle source
# File lib/tabmani/table.rb, line 43
def self.parse_separator(io, separator)

  lines = io.readlines
  matrix = lines.map { |line| line.chomp.split(separator) }
  self.new(matrix: matrix)
end
print_size(string) click to toggle source
projection_ary(lines) click to toggle source

全ての文字列の最大長を要素数とする配列で、 空白文字以外があれば true, 全て空白文字ならば false にした配列。

# File lib/tabmani/table.rb, line 123
def self.projection_ary(lines)
  return [] if lines.empty?
  max_length = lines.max_by{|line| line.size}.size
  results = Array.new(max_length).fill(false)
  lines.each do |line|
    line.chomp.size.times do |i|
      c = line[i]
      next if results[i] == true
      if c == ' '
        next
      else
        results[i] = true
      end
    end
  end
  results
end
whole_length(matrix) click to toggle source
# File lib/tabmani/table.rb, line 155
def self.whole_length(matrix)
  result = 0
  self.max_lengths(matrix).each { |l| result += l}
  result += num_columns(matrix) - 1
end

Public Instance Methods

add_hline(num) click to toggle source

num is the index of row number below the horizontal line.

# File lib/tabmani/table.rb, line 419
def add_hline(num)
  @hlines << num
end
add_sum(key) click to toggle source
# File lib/tabmani/table.rb, line 401
def add_sum(key)
  index = key.to_i - 1
  sum = 0
  @matrix.each do |items|
    str = items[index]
    if str.include?('.')
      sum += str.to_f
    else
      sum += str.to_i
    end
  end
  items = Array.new(num_columns).fill('')
  items[index] = sum.to_s
  add_hline( @matrix.size)
  @matrix << items
end
analyze(io: , keys:) click to toggle source
# File lib/tabmani/table.rb, line 364
def analyze(io: , keys:)
  io.puts

  unless @titles
    @titles = []
    num_columns.times do |i|
      @titles[i] = @matrix[0][i].to_s
    end
  end

  if @matrix.size != 0
    results = []
    results << %w(key head types)
    @titles.size.times do |i|
      results << [(i+1).to_s, @titles[i].strip,
        @matrix.map {|items| items[i]}.sort_by{|j| j.to_s}.uniq.size.to_s
      ]
    end
    Tabmani::Table.dump_column_format(matrix: results, io: io)
  end

  unless keys.empty?
    io.puts
    io.puts "key analysis"
    keys.each do |key|
      io.puts "(key=#{key})"
      values = @matrix.map{|items| items[key.to_i-1] }
      names = values.sort.uniq
      results = []
      names.each { |name| results << [name, values.count(name).to_s] }
      results.sort_by!{|v| v[1].to_i}
      Tabmani::Table.dump_column_format(matrix: results, io: io)
      io.puts
    end
  end
end
dump(io: , style: , just: :left, separator: ' ') click to toggle source

Wrapper for various dump method.

# File lib/tabmani/table.rb, line 202
def dump(io: , style: , just: :left, separator: ' ')
  case style
  when :column ; dump_column_format(io: io, just: just, separator: separator )
  when :csv    ; dump_csv(io: io)
  when :mds    ; dump_md_simple(io: io, just: just )
  when :tex    ; dump_tex(io: io, just: just )
  else
    raise ParseError, "Unknown style: #{style}"
  end
end
dump_column_format(io: $stdout, just: :left, separator: ' ') click to toggle source
# File lib/tabmani/table.rb, line 213
def dump_column_format(io: $stdout, just: :left, separator: ' ')
  self.class.dump_column_format(matrix: @matrix,
                                hlines: @hlines,
                                titles: @titles,
                                io: io,
                                indent: @indent,
                                just: just,
                                separator: separator)
end
dump_csv(io: ) click to toggle source
# File lib/tabmani/table.rb, line 223
def dump_csv(io: )
  matrix = @matrix
  matrix = [@titles, * @matrix] if @titles
  csv_string = CSV.generate do |csv|
    matrix.each { |items| csv << items }
  end
  io.print csv_string
end
dump_md_simple(io: $stdout, just: :left) click to toggle source

markdown simple table style

# File lib/tabmani/table.rb, line 233
def dump_md_simple(io: $stdout, just: :left)
  if @titles
    matrix = [@titles, * @matrix] if @titles
  else
    matrix = @matrix.clone
  end
  matrix.insert(1, line_row)
  self.class.dump_column_format(io: io, matrix: matrix, just: just)
end
dump_tex(io: $stdout, just: :left) click to toggle source
# File lib/tabmani/table.rb, line 243
def dump_tex(io: $stdout, just: :left)
  case just
  when :left   ; just_char = 'l'
  when :right  ; just_char = 'r'
  when :center ; just_char = 'c'
  end

  maxima = max_lengths
  h_size = @matrix.map{|row| row.size }.max

  io.puts "\\begin{tabular}{#{just_char * h_size }}"

  lines = []
  if @titles
    lines << self.class.line_str(items: @titles,
                  lengths: maxima,
                  separator: " & ",
                  just: just,
                  lineend: ' \\\\',
                  indent: 2)
    @hlines << 1
  end

  @matrix.each do |row|
    lines << self.class.line_str(items: row,
                  lengths: maxima,
                  separator: " & ",
                  just: just,
                  lineend: ' \\\\',
                  indent: 2)
  end
  #@hlines = [0, maxima.size] if @hlines.empty?
  @hlines << 0
  @hlines << maxima.size
  @hlines.sort.reverse.each do |index|
    lines.insert(index, "  \\hline")
  end
  io.puts lines.join("\n")
  io.puts "\\end{tabular}"
end
filter(key, val) click to toggle source
# File lib/tabmani/table.rb, line 317
def filter(key, val)
  result = Marshal.load(Marshal.dump(self))
  result.filter!(key, val)
  result
end
filter!(key, val) click to toggle source
# File lib/tabmani/table.rb, line 311
def filter!(key, val)
  @matrix.select! do |items|
    items[key.to_i - 1] == val
  end
end
max_lengths() click to toggle source

return array of max size of item at index

# File lib/tabmani/table.rb, line 357
def max_lengths
  matrix = @matrix
  matrix = [@titles, * @matrix] if @titles
  self.class.max_lengths(matrix)
end
reform(key_x, key_y, key_val) click to toggle source
# File lib/tabmani/table.rb, line 323
def reform(key_x, key_y, key_val)
  x_index = key_x  .to_i - 1
  y_index = key_y  .to_i - 1
  v_index = key_val.to_i - 1

  data_hash = {}
  xs = []
  ys = []
  @matrix.each do |items|
    next if items.empty?
    x = items[x_index]
    y = items[y_index]
    v = items[v_index]
    data_hash[y] ||= {}
    raise DuplicateCellError, "Duplicated condition: #{x}, #{y}" if data_hash[y][x]
    data_hash[y][x] = v
    xs << x
    ys << y
  end

  xs = xs.sort.uniq
  ys = ys.sort.uniq
  matrix = []
  matrix << [''] + xs
  ys.each do |y|
    items = []
    items << y
    xs.each { |x| items << data_hash[y][x] }
    matrix << items
  end
  @matrix = matrix
end
set_title() click to toggle source
# File lib/tabmani/table.rb, line 430
def set_title
  @titles = @matrix.shift
end
show_keys(io: $stdout, separator: ' ') click to toggle source
# File lib/tabmani/table.rb, line 284
def show_keys(io: $stdout, separator: ' ')
  matrix = []
  matrix << line_row

  tmp = []
  max_lengths.size.times do |i|
    tmp << (i + 1).to_s
  end
  matrix << tmp

  self.class.dump_column_format(matrix: matrix, io: io, indent: @indent)
end
strip() click to toggle source

delete spaces head or tail in each items. Destructive

# File lib/tabmani/table.rb, line 424
def strip
  @matrix.map! do |items|
    items.map {|str| str.strip}
  end
end
transpose() click to toggle source
# File lib/tabmani/table.rb, line 305
def transpose
  result = Marshal.load(Marshal.dump(self))
  result.transpose!
  result
end
transpose!() click to toggle source

transpose matrix. empty cell is filled by empty String, ''. thanks: www.tom08.net/entry/2017/12/21/125127

# File lib/tabmani/table.rb, line 300
def transpose!
  max_length = @matrix.max_by(&:size).size
  @matrix = @matrix.map { |m| m.fill('', m.size, max_length - m.size) }.transpose
end

Private Instance Methods

line_row() click to toggle source
# File lib/tabmani/table.rb, line 436
def line_row
  max_lengths.map {|i| '-' * i}
end
num_columns() click to toggle source
# File lib/tabmani/table.rb, line 440
def num_columns
  self.class.num_columns(@matrix)
end