347 lines
7.1 KiB
Ruby
347 lines
7.1 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'rex/ui'
|
|
require 'rex/socket'
|
|
|
|
module Rex
|
|
module Ui
|
|
module Text
|
|
|
|
###
|
|
#
|
|
# Prints text in a tablized format. Pretty lame at the moment, but
|
|
# whatever.
|
|
#
|
|
###
|
|
class Table
|
|
|
|
#
|
|
# Initializes a text table instance using the supplied properties. The
|
|
# Table class supports the following hash attributes:
|
|
#
|
|
# Header
|
|
#
|
|
# The string to display as a heading above the table. If none is
|
|
# specified, no header will be displayed.
|
|
#
|
|
# HeaderIndent
|
|
#
|
|
# The amount of space to indent the header. The default is zero.
|
|
#
|
|
# Columns
|
|
#
|
|
# The array of columns that will exist within the table.
|
|
#
|
|
# Rows
|
|
#
|
|
# The array of rows that will exist.
|
|
#
|
|
# Width
|
|
#
|
|
# The maximum width of the table in characters.
|
|
#
|
|
# Indent
|
|
#
|
|
# The number of characters to indent the table.
|
|
#
|
|
# CellPad
|
|
#
|
|
# The number of characters to put between each horizontal cell.
|
|
#
|
|
# Prefix
|
|
#
|
|
# The text to prefix before the table.
|
|
#
|
|
# Postfix
|
|
#
|
|
# The text to affix to the end of the table.
|
|
#
|
|
# Sortindex
|
|
#
|
|
# The column to sort the table on, -1 disables sorting.
|
|
#
|
|
def initialize(opts = {})
|
|
self.header = opts['Header']
|
|
self.headeri = opts['HeaderIndent'] || 0
|
|
self.columns = opts['Columns'] || []
|
|
# updated below if we got a "Rows" option
|
|
self.rows = []
|
|
|
|
self.width = opts['Width'] || 80
|
|
self.indent = opts['Indent'] || 0
|
|
self.cellpad = opts['CellPad'] || 2
|
|
self.prefix = opts['Prefix'] || ''
|
|
self.postfix = opts['Postfix'] || ''
|
|
self.colprops = []
|
|
|
|
self.sort_index = opts['SortIndex'] || 0
|
|
|
|
# Default column properties
|
|
self.columns.length.times { |idx|
|
|
self.colprops[idx] = {}
|
|
self.colprops[idx]['MaxWidth'] = self.columns[idx].length
|
|
}
|
|
|
|
# ensure all our internal state gets updated with the given rows by
|
|
# using add_row instead of just adding them to self.rows. See #3825.
|
|
opts['Rows'].each { |row| add_row(row) } if opts['Rows']
|
|
|
|
# Merge in options
|
|
if (opts['ColProps'])
|
|
opts['ColProps'].each_key { |col|
|
|
idx = self.columns.index(col)
|
|
|
|
if (idx)
|
|
self.colprops[idx].merge!(opts['ColProps'][col])
|
|
end
|
|
}
|
|
end
|
|
|
|
end
|
|
|
|
#
|
|
# Converts table contents to a string.
|
|
#
|
|
def to_s
|
|
str = prefix.dup
|
|
str << header_to_s || ''
|
|
str << columns_to_s || ''
|
|
str << hr_to_s || ''
|
|
|
|
sort_rows
|
|
rows.each { |row|
|
|
if (is_hr(row))
|
|
str << hr_to_s
|
|
else
|
|
str << row_to_s(row)
|
|
end
|
|
}
|
|
|
|
str << postfix
|
|
|
|
return str
|
|
end
|
|
|
|
#
|
|
# Converts table contents to a csv
|
|
#
|
|
def to_csv
|
|
str = ''
|
|
str << ( columns.join(",") + "\n" )
|
|
rows.each { |row|
|
|
next if is_hr(row)
|
|
str << ( row.map{|x|
|
|
x = x.to_s
|
|
|
|
x.gsub(/[\r\n]/, ' ').gsub(/\s+/, ' ').gsub('"', '""')
|
|
}.map{|x| "\"#{x}\"" }.join(",") + "\n" )
|
|
}
|
|
str
|
|
end
|
|
|
|
#
|
|
#
|
|
# Returns the header string.
|
|
#
|
|
def header_to_s # :nodoc:
|
|
if (header)
|
|
pad = " " * headeri
|
|
|
|
return pad + header + "\n" + pad + "=" * header.length + "\n\n"
|
|
end
|
|
|
|
return ''
|
|
end
|
|
|
|
#
|
|
# Prints the contents of the table.
|
|
#
|
|
def print
|
|
puts to_s
|
|
end
|
|
|
|
#
|
|
# Adds a row using the supplied fields.
|
|
#
|
|
def <<(fields)
|
|
add_row(fields)
|
|
end
|
|
|
|
#
|
|
# Adds a row with the supplied fields.
|
|
#
|
|
def add_row(fields = [])
|
|
if fields.length != self.columns.length
|
|
raise RuntimeError, 'Invalid number of columns!'
|
|
end
|
|
fields.each_with_index { |field, idx|
|
|
if (colprops[idx]['MaxWidth'] < field.to_s.length)
|
|
colprops[idx]['MaxWidth'] = field.to_s.length
|
|
end
|
|
}
|
|
|
|
rows << fields
|
|
end
|
|
|
|
#
|
|
# Sorts the rows based on the supplied index of sub-arrays
|
|
# If the supplied index is an IPv4 address, handle it differently, but
|
|
# avoid actually resolving domain names.
|
|
#
|
|
def sort_rows(index=sort_index)
|
|
return if index == -1
|
|
return unless rows
|
|
rows.sort! do |a,b|
|
|
if a[index].nil?
|
|
-1
|
|
elsif b[index].nil?
|
|
1
|
|
elsif Rex::Socket.dotted_ip?(a[index]) and Rex::Socket.dotted_ip?(b[index])
|
|
Rex::Socket::addr_atoi(a[index]) <=> Rex::Socket::addr_atoi(b[index])
|
|
elsif a[index] =~ /^[0-9]+$/ and b[index] =~ /^[0-9]+$/
|
|
a[index].to_i <=> b[index].to_i
|
|
else
|
|
a[index] <=> b[index] # assumes otherwise comparable.
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Adds a horizontal line.
|
|
#
|
|
def add_hr
|
|
rows << '__hr__'
|
|
end
|
|
|
|
#
|
|
# Returns new sub-table with headers and rows maching column names submitted
|
|
#
|
|
def [](*col_names)
|
|
tbl = self.class.new('Indent' => self.indent,
|
|
'Header' => self.header,
|
|
'Columns' => col_names)
|
|
indexes = []
|
|
|
|
col_names.each do |col_name|
|
|
index = self.columns.index(col_name)
|
|
raise RuntimeError, "Invalid column name #{col_name}" if index.nil?
|
|
indexes << index
|
|
end
|
|
|
|
self.rows.each do |old_row|
|
|
new_row = []
|
|
indexes.map {|i| new_row << old_row[i]}
|
|
tbl << new_row
|
|
end
|
|
|
|
return tbl
|
|
end
|
|
|
|
|
|
alias p print
|
|
|
|
attr_accessor :header, :headeri # :nodoc:
|
|
attr_accessor :columns, :rows, :colprops # :nodoc:
|
|
attr_accessor :width, :indent, :cellpad # :nodoc:
|
|
attr_accessor :prefix, :postfix # :nodoc:
|
|
attr_accessor :sort_index # :nodoc:
|
|
|
|
protected
|
|
|
|
#
|
|
# Defaults cell widths and alignments.
|
|
#
|
|
def defaults # :nodoc:
|
|
self.columns.length.times { |idx|
|
|
}
|
|
end
|
|
|
|
#
|
|
# Checks to see if the row is an hr.
|
|
#
|
|
def is_hr(row) # :nodoc:
|
|
return ((row.kind_of?(String)) && (row == '__hr__'))
|
|
end
|
|
|
|
#
|
|
# Converts the columns to a string.
|
|
#
|
|
def columns_to_s # :nodoc:
|
|
nameline = ' ' * indent
|
|
barline = nameline.dup
|
|
last_col = nil
|
|
last_idx = nil
|
|
columns.each_with_index { |col,idx|
|
|
if (last_col)
|
|
nameline << pad(' ', last_col, last_idx)
|
|
|
|
remainder = colprops[last_idx]['MaxWidth'] - last_col.length
|
|
if (remainder < 0)
|
|
remainder = 0
|
|
end
|
|
barline << (' ' * (cellpad + remainder))
|
|
end
|
|
nameline << col
|
|
barline << ('-' * col.length)
|
|
|
|
last_col = col
|
|
last_idx = idx
|
|
}
|
|
|
|
return "#{nameline}\n#{barline}"
|
|
end
|
|
|
|
#
|
|
# Converts an hr to a string.
|
|
#
|
|
def hr_to_s # :nodoc:
|
|
return "\n"
|
|
end
|
|
|
|
#
|
|
# Converts a row to a string.
|
|
#
|
|
def row_to_s(row) # :nodoc:
|
|
line = ' ' * indent
|
|
last_cell = nil
|
|
last_idx = nil
|
|
row.each_with_index { |cell, idx|
|
|
if (last_cell)
|
|
line << pad(' ', last_cell.to_s, last_idx)
|
|
end
|
|
# line << pad(' ', cell.to_s, idx)
|
|
# Limit wide cells
|
|
if colprops[idx]['MaxChar']
|
|
last_cell = cell.to_s[0..colprops[idx]['MaxChar'].to_i]
|
|
line << last_cell
|
|
else
|
|
line << cell.to_s
|
|
last_cell = cell
|
|
end
|
|
last_idx = idx
|
|
}
|
|
|
|
return line + "\n"
|
|
end
|
|
|
|
#
|
|
# Pads out with the supplied character for the remainder of the space given
|
|
# some text and a column index.
|
|
#
|
|
def pad(chr, buf, colidx, use_cell_pad = true) # :nodoc:
|
|
remainder = colprops[colidx]['MaxWidth'] - buf.length
|
|
val = chr * remainder;
|
|
|
|
if (use_cell_pad)
|
|
val << ' ' * cellpad
|
|
end
|
|
|
|
return val
|
|
end
|
|
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|