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
|
require 'ansi/terminal'
module ANSI
#
class Columns
# Create a column-based layout.
#
# @param [String,Array] list
# Multiline String or Array of strings to columnize.
#
# @param [Hash] options
# Options to customize columnization.
#
# @option options [Fixnum] :columns
# Number of columns.
#
# @option options [Symbol] :align
# Column alignment, either :left, :right or :center.
#
# @option options [String,Fixnum] :padding
# String or number or spaces to append to each column.
#
# The +format+ block MUST return ANSI codes.
def initialize(list, options={}, &format)
self.list = list
self.columns = options[:columns] || options[:cols]
self.padding = options[:padding] || 1
self.align = options[:align] || :left
#self.ansi = options[:ansi]
self.format = format
#@columns = nil if @columns == 0
end
#
def inspect
"#<#{self.class}:#{object_id} #{list.inspect} x #{columns}>"
end
# List layout into columns. Each new line is taken to be
# a row-column cell.
attr :list
def list=(list)
case list
when ::String
@list = list.lines.to_a.map{ |e| e.chomp("\n") }
when ::Array
@list = list.map{ |e| e.to_s }
end
end
# Default number of columns to display. If nil then the number
# of coumns is estimated from the size of the terminal.
attr :columns
# Set column count ensuring value is either an integer or nil.
# The the value given is zero, it will be taken to mean the same
# as nil, which means fit-to-screen.
def columns=(integer)
integer = integer.to_i
@columns = (integer.zero? ? nil : integer)
end
# Padding size to apply to cells.
attr :padding
# Set padding to string or number (of spaces).
def padding=(pad)
case pad
when Numeric
@padding = ' ' * pad.to_i
else
@padding = pad.to_s
end
end
# Alignment to apply to cells.
attr :align
# Set alignment ensuring value is a symbol.
#
# @param [#to_sym] symbol
# Either `:right`, `:left` or `:center`.
#
# @return [Symbol] The given symbol.
def align=(symbol)
symbol = symbol.to_sym
raise ArgumentError, "invalid alignment -- #{symbol.inspect}" \
unless [:left, :right, :center].include?(symbol)
@align = symbol
end
# Formating to apply to cells.
attr :format
# Set formatting procedure. The procedure must return
# ANSI codes, suitable for passing to String#ansi method.
def format=(procedure)
@format = procedure ? procedure.to_proc : nil
end
# TODO: Should #to_s also take options and formatting block?
# Maybe instead have hoin take all these and leave #to_s bare.
# Return string in column layout. The number of columns is determined
# by the `columns` property or overriden by +cols+ argument.
def to_s(cols=nil)
to_s_columns(cols || columns)
end
#
def join(cols=nil)
to_s_columns(cols || columns)
end
private
# Layout string lines into columns.
#
# @todo Put in empty strings for blank cells.
# @todo Centering look like it's off by one to the right.
#
def to_s_columns(columns=nil)
lines = list.to_a
count = lines.size
max = lines.map{ |l| l.size }.max
if columns.nil?
width = Terminal.terminal_width
columns = (width / (max + padding.size)).to_i
end
rows = []
mod = (count / columns.to_f).to_i
mod += 1 if count % columns != 0
lines.each_with_index do |line, index|
(rows[index % mod] ||=[]) << line.strip
end
pad = padding
tmp = template(max, pad)
str = ""
rows.each_with_index do |row, ri|
row.each_with_index do |cell, ci|
ansi_codes = ansi_formatting(cell, ci, ri)
if ansi_codes.empty?
str << (tmp % cell)
else
str << (tmp % cell).ansi(*ansi_codes)
end
end
str.rstrip!
str << "\n"
end
str
end
# Aligns the cell left or right.
def template(max, pad)
case align
when :center, 'center'
offset = " " * (max / 2)
"#{offset}%#{max}s#{offset}#{pad}"
when :right, 'right'
"%#{max}s#{pad}"
else
"%-#{max}s#{pad}"
end
end
# Used to apply ANSI formatting to each cell.
def ansi_formatting(cell, col, row)
if @format
case @format.arity
when 0
f = @format[]
when 1
f = @format[cell]
when 2
f = @format[col, row]
else
f = @format[cell, col, row]
end
else
f = nil
end
[f].flatten.compact
end
end
end
|