From my bag of tricks…
On occasion, I run into the need to present some kind of table information on to a console/terminal session so that the information is readable. After doing this several times, I decided to write a text table class so that i didn’t have to worry about formatting the table within my code. I just set the table up with column titles and column widths and just focus on the data.
Here’s the code:
#
# texttable.rb -- testing tool to output a nicely formatted table.
#
# John Allen, June 2005
# June 2010 -- Added support for word wrap fields
#
class TextTable
#
# tableinfo = Hash.new {
# "name" => Array(:string),
# "width" => Array(:fixnum),
# ["linebetweenrows" => Boolean,]
# ["hdrlinechar" => "=",]
# ["rowlinechar" => "-",]
# ["wordwrap" => Boolean]
# }
attr_accessor :names, :widths, :hdr_print_flg, :lbr_flg, :hdrchar, :linechar, :wordwrap
@hdr_print_flg = false
@ldr_flg = false
@wordwrap = false
def initialize(tableinfo)
@names = tableinfo["name"]
@widths = tableinfo["width"]
if tableinfo["linebetweenrows"]
@lbr_flg = true
end
if tableinfo["wordwrap"]
@wordwrap = true
end
@hdrchar = tableinfo["hdrlinechar"] || "="
@linechar = tableinfo["rowlinechar"] || "-"
end
def printrow(a)
# a = Array
buf = ""
if not @hdr_print_flg
buf << printHdr()
end
buf << "|"
extra = []
a.each_with_index do |n,i|
if n.length > @widths[i] ## check to see if value is bigger than field; chop if so
b = n.slice(0..(@widths[i] -1))
extra[i] = n.slice(@widths[i]..-1)
else
b = n
end
buf << " #{b}#{" "*(@widths[i] - b.length)}|"
end
buf << "\n"
if @wordwrap and not extra.empty? ## Word Wrap
eflg = true
while eflg ## While stuff to word wrap
eflg = false
buf << "|"
extra.each_with_index do |n,i|
if not n.nil?
if n.length > @widths[i] ## check to see if value is bigger than field; chop if so
b = n.slice(0..(@widths[i] -1))
extra[i] = n.slice(@widths[i]..-1)
eflg = true ## still more to word wrap!!
else
b = n
extra[i] = nil
end
buf << " #{b}#{" "*(@widths[i] - b.length)}|"
else
buf << " #{" "*@widths[i]}|" ## add blank space for non-wordwrap field
end
end
buf << "\n"
end
end
buf << _line(@linechar) if @lbr_flg
return buf
end
def printHdr
buf = _line(@hdrchar)
buf << "|"
@names.each_with_index do |n,i|
buf << " #{n}#{" "*(@widths[i] - n.length)}|"
end
buf << "\n"
buf << _line(@hdrchar)
@hdr_print_flg = true
return buf
end
def printLine
buf = _line(@linechar)
return buf
end
#-------------------------------------------------------------------------------------------------------#
private
#-------------------------------------------------------------------------------------------------------#
def _line(char)
b = "+"
@names.each_with_index do |n,i|
b << char*@widths[i]
b << "#{char}+"
end
b << "\n"
return b
end
end
if __FILE__ == $0
tt = {
"name" => ["First","Last","City","State"],
"width" => [15,15,15,6],
"linebetweenrows" => false
}
names = [
["John","Allen","Redmond","WA"],
["Herman","Gonzales","Mill Creek","WA"],
["Jimmy","Doogle","Bothell","WA"],
["Jane","Goodman","Seattle","WA"]
]
table = TextTable.new(tt)
buf = ""
names.each do |name|
buf << table.printrow(name)
end
buf << table.printLine
puts buf
end
Running the example code at the bottom prints out the following result:
C:\Server4\Dev\Ruby\exo>ruby texttable.rb
+================+================+================+=======+
| First | Last | City | State |
+================+================+================+=======+
| John | Allen | Redmond | WA |
| Herman | Gonzales | Mill Creek | WA |
| Jimmy | Doogle | Bothell | WA |
| Jane | Goodman | Seattle | WA |
+----------------+----------------+----------------+-------+
the printrow() method takes an array of String values to print out. You can have a line printed out between each row if you set the linebetweenrows hash value to ‘true’ (it defaults to ‘false’). There is not a lot of error checking (as in, you can crash the program if you feed the printrow() method an array that is shorter than the number of columns), but since I mainly use it for testing or utilities, I didn’t put a lot in. Its a very handy tool to have around.
Update: I added support for word wrap in all fields recently, so I have updated the code above with that version.