1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
module Text #:nodoc:
class Table
# An array of table headers
#
attr_accessor :head
# An array of arrays (rows or separators)
#
attr_accessor :rows
# An array representing the foot of the table
attr_accessor :foot
# The vertical boundary. Default is "<tt>-</tt>"
#
attr_accessor :vertical_boundary
# The horizontal boundary. Default is "<tt>|</tt>"
#
attr_accessor :horizontal_boundary
# The boundary intersection. Default is "<tt>+</tt>"
#
attr_accessor :boundary_intersection
# The amount of padding (spaces) added to the left and right of cell contents. Default is <tt>1</tt>
#
attr_accessor :horizontal_padding
# You could create a Text::Table object by passing an options hash:
#
# table = Text::Table.new(:head => ['A', 'B'], :rows => [['a1', 'b1'], ['a2', 'b2']])
#
# Or by passing a block:
#
# table = Text::Table.new do |t|
# t.head = ['A', 'B']
# t.rows = [['a1', 'b1']]
# t.rows << ['a2', 'b2']
# end
#
# table.to_s
#
# # +----+----+
# # | A | B |
# # +----+----+
# # | a1 | b1 |
# # | a2 | b2 |
# # +----+----+
#
#
# ==== Aligning Cells and Spanning Columns
#
# Alignment and column span can be specified by passing a cell as a Hash object.
#
# The acceptable aligments are <tt>:left</tt>, <tt>:center</tt> and <tt>:right</tt>.
#
# Cells and footers are aligned to the left by default, while headers are centered by default.
#
# table = Text::Table.new do |t|
# t.head = ['Heading A', 'Heading B']
# t.rows << ['a1', 'b1']
# t.rows << ['a2', {:value => 'b2', :align => :right}]
# t.rows << ['a3', 'b3']
# t.rows << [{:value => 'a4', :colspan => 2, :align => :center}]
# end
#
# puts table
#
# # +-----------+-----------+
# # | Heading A | Heading B |
# # +-----------+-----------+
# # | a1 | b1 |
# # | a2 | b2 |
# # | a3 | b3 |
# # | a4 |
# # +-----------+-----------+
#
#
# ==== Adding a Separator
#
# You can add a separator by inserting <tt>:separator</tt> symbols between the rows.
#
# Text::Table.new :rows => [
# ['a', 'b'],
# ['c', 'd'],
# :separator,
# ['e', 'f'],
# :separator,
# ['g', 'h']
# ]
#
# # +---+---+
# # | a | b |
# # | c | d |
# # +---+---+
# # | e | f |
# # +---+---+
# # | g | h |
# # +---+---+
#
#
# ==== Other Options
#
# Cell padding and table boundaries can be modified.
#
# Text::Table.new :rows => [['a', 'b'], ['c', 'd']],
# :horizontal_padding => 3,
# :vertical_boundary => '=',
# :horizontal_boundary => ':',
# :boundary_intersection => 'O'
#
# # O=======O=======O
# # : a : b :
# # : c : d :
# # O=======O=======O
#
def initialize(options = {})
@vertical_boundary = options[:vertical_boundary ] || '-'
@horizontal_boundary = options[:horizontal_boundary ] || '|'
@boundary_intersection = options[:boundary_intersection] || '+'
@horizontal_padding = options[:horizontal_padding ] || 1
@head = options[:head]
@rows = options[:rows] || []
@foot = options[:foot]
yield self if block_given?
end
def text_table_rows #:nodoc:
rows.to_a.map {|row_input| Row.new(row_input, self)}
end
def text_table_head #:nodoc:
Row.new(
head.map {|h| hashify(h, {:align => :center})},
self
) if head
end
def text_table_foot #:nodoc:
Row.new(foot, self) if foot
end
def all_text_table_rows #:nodoc:
all = text_table_rows
all.unshift text_table_head if head
all << text_table_foot if foot
all
end
def column_widths #:nodoc:
@column_widths ||= \
all_text_table_rows.reject {|row| row.cells == :separator}.map do |row|
row.cells.map {|cell| [(cell.value.length/cell.colspan.to_f).ceil] * cell.colspan}.flatten
end.transpose.map(&:max)
end
def separator #:nodoc:
([@boundary_intersection] * 2).join(
column_widths.map {|column_width| @vertical_boundary * (column_width + 2*@horizontal_padding)}.join(@boundary_intersection)
) + "\n"
end
# Renders a pretty plain-text table.
#
def to_s
rendered_rows = [separator] + text_table_rows.map(&:to_s) + [separator]
rendered_rows.unshift [separator, text_table_head.to_s] if head
rendered_rows << [text_table_foot.to_s, separator] if foot
rendered_rows.join
end
# Aligns the cells and the footer of a column.
#
# table = Text::Table.new :rows => [%w(a bb), %w(aa bbb), %w(aaa b)]
# puts table
#
# # +-----+-----+
# # | a | bb |
# # | aa | bbb |
# # | aaa | b |
# # +-----+-----+
#
# table.align_column 2, :right
#
# # +-----+-----+
# # | a | bb |
# # | aa | bbb |
# # | aaa | b |
# # +-----+-----+
#
# Note that headers, spanned cells and cells with explicit alignments are not affected by <tt>align_column</tt>.
#
def align_column(column_number, alignment)
set_alignment = Proc.new do |row, column_number_block, alignment_block|
cell = row.find do |cell_row|
row[0...row.index(cell_row)].map {|c| c.is_a?(Hash) ? c[:colspan] || 1 : 1}.inject(0, &:+) == column_number_block - 1
end
row[row.index(cell)] = hashify(cell, {:align => alignment_block}) if cell and not(cell.is_a?(Hash) && cell[:colspan].to_i > 0)
end
rows.each do |row|
next if row == :separator
set_alignment.call(row, column_number, alignment)
end
set_alignment.call(foot, column_number, alignment) if foot
return self
end
def hashify(cell, defaults = {}) #:nodoc:
defaults.merge(cell.is_a?(Hash) ? cell : {:value => cell})
end
def inspect
"#<#{self.class}:0x#{self.__id__.to_s(16)}>"
end
end
end
|