285 lines
9.2 KiB
Ruby
285 lines
9.2 KiB
Ruby
# coding: utf-8
|
|
require 'forwardable'
|
|
|
|
module Terminal
|
|
class Table
|
|
|
|
class Border
|
|
|
|
attr_accessor :data, :top, :bottom, :left, :right
|
|
def initialize
|
|
@top, @bottom, @left, @right = true, true, true, true
|
|
end
|
|
def []=(key, val)
|
|
@data[key] = val
|
|
end
|
|
def [](key)
|
|
@data[key]
|
|
end
|
|
def initialize_dup(other)
|
|
super
|
|
@data = other.data.dup
|
|
end
|
|
def remove_verticals
|
|
self.class.const_get("VERTICALS").each { |key| @data[key] = "" }
|
|
self.class.const_get("INTERSECTIONS").each { |key| @data[key] = "" }
|
|
end
|
|
def remove_horizontals
|
|
self.class.const_get("HORIZONTALS").each { |key| @data[key] = "" }
|
|
end
|
|
|
|
# If @left, return the edge else empty-string.
|
|
def maybeleft(key) ; @left ? @data[key] : '' ; end
|
|
|
|
# If @right, return the edge else empty-string.
|
|
def mayberight(key) ; @right ? @data[key] : '' ; end
|
|
|
|
end
|
|
|
|
class AsciiBorder < Border
|
|
HORIZONTALS = %i[x]
|
|
VERTICALS = %i[y]
|
|
INTERSECTIONS = %i[i]
|
|
|
|
def initialize
|
|
super
|
|
@data = { x: "-", y: "|", i: "+" }
|
|
end
|
|
|
|
# Get vertical border elements
|
|
# @return [Array] 3-element list of [left, center, right]
|
|
def vertical
|
|
[maybeleft(:y), @data[:y], mayberight(:y)] # left, center, right
|
|
end
|
|
|
|
# Get horizontal border elements
|
|
# @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
|
|
def horizontal(_type)
|
|
x, i = @data[:x], @data[:i]
|
|
[maybeleft(:i), x, i, mayberight(:i), i, i]
|
|
end
|
|
end
|
|
|
|
class MarkdownBorder < AsciiBorder
|
|
def initialize
|
|
super
|
|
@top, @bottom = false, false
|
|
@data = { x: "-", y: "|", i: "|" }
|
|
end
|
|
end
|
|
|
|
class UnicodeBorder < Border
|
|
|
|
ALLOWED_SEPARATOR_BORDER_STYLES = %i[
|
|
top bot
|
|
div dash dot3 dot4
|
|
thick thick_dash thick_dot3 thick_dot4
|
|
heavy heavy_dash heavy_dot3 heavy_dot4
|
|
bold bold_dash bold_dot3 bold_dot4
|
|
double
|
|
]
|
|
|
|
HORIZONTALS = %i[x sx ax bx nx bx_dot3 bx_dot4 bx_dash x_dot3 x_dot4 x_dash]
|
|
VERTICALS = %i[y yw ye]
|
|
INTERSECTIONS = %i[nw n ne nd
|
|
aw ai ae ad au
|
|
bw bi be bd bu
|
|
w i e dn up
|
|
sw s se su]
|
|
def initialize
|
|
super
|
|
@data = {
|
|
nil => nil,
|
|
nw: "┌", nx: "─", n: "┬", ne: "┐",
|
|
yw: "│", y: "│", ye: "│",
|
|
aw: "╞", ax: "═", ai: "╪", ae: "╡", ad: '╤', au: "╧", # double
|
|
bw: "┝", bx: "━", bi: "┿", be: "┥", bd: '┯', bu: "┷", # heavy/bold/thick
|
|
w: "├", x: "─", i: "┼", e: "┤", dn: "┬", up: "┴", # normal div
|
|
sw: "└", sx: "─", s: "┴", se: "┘",
|
|
# alternative dots/dashes
|
|
x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
|
|
bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
|
|
}
|
|
end
|
|
# Get vertical border elements
|
|
# @return [Array] 3-element list of [left, center, right]
|
|
def vertical
|
|
[maybeleft(:yw), @data[:y], mayberight(:ye)]
|
|
end
|
|
|
|
# Get horizontal border elements
|
|
# @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
|
|
def horizontal(type)
|
|
raise ArgumentError, "Border type is #{type.inspect}, must be one of #{ALLOWED_SEPARATOR_BORDER_STYLES.inspect}" unless ALLOWED_SEPARATOR_BORDER_STYLES.include?(type)
|
|
lookup = case type
|
|
when :top
|
|
[:nw, :nx, :n, :ne, :n, nil]
|
|
when :bot
|
|
[:sw, :sx, :s, :se, nil, :s]
|
|
when :double
|
|
# typically used for the separator below the heading row or above a footer row)
|
|
[:aw, :ax, :ai, :ae, :ad, :au]
|
|
when :thick, :thick_dash, :thick_dot3, :thick_dot4,
|
|
:heavy, :heavy_dash, :heavy_dot3, :heavy_dot4,
|
|
:bold, :bold_dash, :bold_dot3, :bold_dot4
|
|
# alternate thick/bold border
|
|
xref = type.to_s.sub(/^(thick|heavy|bold)/,'bx').to_sym
|
|
[:bw, xref, :bi, :be, :bd, :bu]
|
|
when :dash, :dot3, :dot4
|
|
# alternate thin dividers
|
|
xref = "x_#{type}".to_sym
|
|
[:w, xref, :i, :e, :dn, :up]
|
|
else # :div (center, non-emphasized)
|
|
[:w, :x, :i, :e, :dn, :up]
|
|
end
|
|
rval = lookup.map { |key| @data.fetch(key) }
|
|
rval[0] = '' unless @left
|
|
rval[3] = '' unless @right
|
|
rval
|
|
end
|
|
end
|
|
|
|
# Unicode Border With rounded edges
|
|
class UnicodeRoundBorder < UnicodeBorder
|
|
def initialize
|
|
super
|
|
@data.merge!({nw: '╭', ne: '╮', sw: '╰', se: '╯'})
|
|
end
|
|
end
|
|
|
|
# Unicode Border with thick outer edges
|
|
class UnicodeThickEdgeBorder < UnicodeBorder
|
|
def initialize
|
|
super
|
|
@data = {
|
|
nil => nil,
|
|
nw: "┏", nx: "━", n: "┯", ne: "┓", nd: nil,
|
|
yw: "┃", y: "│", ye: "┃",
|
|
aw: "┣", ax: "═", ai: "╪", ae: "┫", ad: '╤', au: "╧", # double
|
|
bw: "┣", bx: "━", bi: "┿", be: "┫", bd: '┯', bu: "┷", # heavy/bold/thick
|
|
w: "┠", x: "─", i: "┼", e: "┨", dn: "┬", up: "┴", # normal div
|
|
sw: "┗", sx: "━", s: "┷", se: "┛", su: nil,
|
|
# alternative dots/dashes
|
|
x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
|
|
bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
|
|
}
|
|
end
|
|
end
|
|
|
|
# A Style object holds all the formatting information for a Table object
|
|
#
|
|
# To create a table with a certain style, use either the constructor
|
|
# option <tt>:style</tt>, the Table#style object or the Table#style= method
|
|
#
|
|
# All these examples have the same effect:
|
|
#
|
|
# # by constructor
|
|
# @table = Table.new(:style => {:padding_left => 2, :width => 40})
|
|
#
|
|
# # by object
|
|
# @table.style.padding_left = 2
|
|
# @table.style.width = 40
|
|
#
|
|
# # by method
|
|
# @table.style = {:padding_left => 2, :width => 40}
|
|
#
|
|
# To set a default style for all tables created afterwards use Style.defaults=
|
|
#
|
|
# Terminal::Table::Style.defaults = {:width => 80}
|
|
#
|
|
class Style
|
|
extend Forwardable
|
|
def_delegators :@border, :vertical, :horizontal, :remove_verticals, :remove_horizontals
|
|
|
|
@@defaults = {
|
|
:border => AsciiBorder.new,
|
|
:padding_left => 1, :padding_right => 1,
|
|
:margin_left => '',
|
|
:width => nil, :alignment => nil,
|
|
:all_separators => false,
|
|
}
|
|
|
|
## settors/gettor for legacy ascii borders
|
|
def border_x=(val) ; @border[:x] = val ; end
|
|
def border_y=(val) ; @border[:y] = val ; end
|
|
def border_i=(val) ; @border[:i] = val ; end
|
|
def border_y ; @border[:y] ; end
|
|
def border_y_width ; Util::ansi_escape(@border[:y]).length ; end
|
|
|
|
# Accessor for instance of Border
|
|
attr_reader :border
|
|
def border=(val)
|
|
if val.is_a? Symbol
|
|
# convert symbol name like :foo_bar to get class FooBarBorder
|
|
klass_str = val.to_s.split('_').collect(&:capitalize).join + "Border"
|
|
begin
|
|
klass = Terminal::Table::const_get(klass_str)
|
|
@border = klass.new
|
|
rescue NameError
|
|
raise "Cannot lookup class Terminal::Table::#{klass_str} from symbol #{val.inspect}"
|
|
end
|
|
else
|
|
@border = val
|
|
end
|
|
end
|
|
|
|
def border_top=(val) ; @border.top = val ; end
|
|
def border_bottom=(val) ; @border.bottom = val ; end
|
|
def border_left=(val) ; @border.left = val ; end
|
|
def border_right=(val) ; @border.right = val ; end
|
|
|
|
def border_top ; @border.top ; end
|
|
def border_bottom ; @border.bottom ; end
|
|
def border_left ; @border.left ; end
|
|
def border_right ; @border.right ; end
|
|
|
|
|
|
attr_accessor :padding_left
|
|
attr_accessor :padding_right
|
|
|
|
attr_accessor :margin_left
|
|
|
|
attr_accessor :width
|
|
attr_accessor :alignment
|
|
|
|
attr_accessor :all_separators
|
|
|
|
|
|
def initialize options = {}
|
|
apply self.class.defaults.merge(options)
|
|
end
|
|
|
|
def apply options
|
|
options.each do |m, v|
|
|
__send__ "#{m}=", v
|
|
end
|
|
end
|
|
|
|
class << self
|
|
def defaults
|
|
klass_defaults = @@defaults.dup
|
|
# border is an object that needs to be duplicated on instantiation,
|
|
# otherwise everything will be referencing the same object-id.
|
|
klass_defaults[:border] = klass_defaults[:border].dup
|
|
klass_defaults
|
|
end
|
|
|
|
def defaults= options
|
|
@@defaults = defaults.merge(options)
|
|
end
|
|
|
|
end
|
|
|
|
def on_change attr
|
|
method_name = :"#{attr}="
|
|
old_method = method method_name
|
|
define_singleton_method(method_name) do |value|
|
|
old_method.call value
|
|
yield attr.to_sym, value
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|