This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user