terminal-table 1.7.2 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +5 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +49 -0
- data/History.rdoc +66 -0
- data/LICENSE.txt +21 -0
- data/README.md +417 -0
- data/Rakefile +10 -4
- data/examples/data.csv +4 -0
- data/examples/examples.rb +0 -0
- data/examples/examples_unicode.rb +89 -0
- data/examples/issue100.rb +34 -0
- data/examples/issue111.rb +4 -0
- data/examples/issue118.rb +36 -0
- data/examples/issue95.rb +42 -0
- data/examples/show_csv_table.rb +34 -0
- data/examples/strong_separator.rb +23 -0
- data/lib/terminal-table.rb +2 -2
- data/lib/terminal-table/cell.rb +8 -8
- data/lib/terminal-table/row.rb +18 -4
- data/lib/terminal-table/separator.rb +56 -4
- data/lib/terminal-table/style.rb +218 -10
- data/lib/terminal-table/table.rb +49 -18
- data/lib/terminal-table/util.rb +13 -0
- data/lib/terminal-table/version.rb +1 -1
- data/terminal-table.gemspec +3 -3
- metadata +32 -15
- data/README.rdoc +0 -238
data/lib/terminal-table/style.rb
CHANGED
@@ -1,5 +1,171 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'forwardable'
|
3
|
+
|
1
4
|
module Terminal
|
2
5
|
class Table
|
6
|
+
|
7
|
+
class Border
|
8
|
+
|
9
|
+
attr_accessor :data, :top, :bottom, :left, :right
|
10
|
+
def initialize
|
11
|
+
@top, @bottom, @left, @right = true, true, true, true
|
12
|
+
end
|
13
|
+
def []=(key, val)
|
14
|
+
@data[key] = val
|
15
|
+
end
|
16
|
+
def [](key)
|
17
|
+
@data[key]
|
18
|
+
end
|
19
|
+
def initialize_dup(other)
|
20
|
+
super
|
21
|
+
@data = other.data.dup
|
22
|
+
end
|
23
|
+
def remove_verticals
|
24
|
+
self.class.const_get("VERTICALS").each { |key| @data[key] = "" }
|
25
|
+
self.class.const_get("INTERSECTIONS").each { |key| @data[key] = "" }
|
26
|
+
end
|
27
|
+
def remove_horizontals
|
28
|
+
self.class.const_get("HORIZONTALS").each { |key| @data[key] = "" }
|
29
|
+
end
|
30
|
+
|
31
|
+
# If @left, return the edge else empty-string.
|
32
|
+
def maybeleft(key) ; @left ? @data[key] : '' ; end
|
33
|
+
|
34
|
+
# If @right, return the edge else empty-string.
|
35
|
+
def mayberight(key) ; @right ? @data[key] : '' ; end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class AsciiBorder < Border
|
40
|
+
HORIZONTALS = %i[x]
|
41
|
+
VERTICALS = %i[y]
|
42
|
+
INTERSECTIONS = %i[i]
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
super
|
46
|
+
@data = { x: "-", y: "|", i: "+" }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get vertical border elements
|
50
|
+
# @return [Array] 3-element list of [left, center, right]
|
51
|
+
def vertical
|
52
|
+
[maybeleft(:y), @data[:y], mayberight(:y)] # left, center, right
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get horizontal border elements
|
56
|
+
# @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
|
57
|
+
def horizontal(_type)
|
58
|
+
x, i = @data[:x], @data[:i]
|
59
|
+
[maybeleft(:i), x, i, mayberight(:i), i, i]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class MarkdownBorder < AsciiBorder
|
64
|
+
def initialize
|
65
|
+
super
|
66
|
+
@top, @bottom = false, false
|
67
|
+
@data = { x: "-", y: "|", i: "|" }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class UnicodeBorder < Border
|
72
|
+
|
73
|
+
ALLOWED_SEPARATOR_BORDER_STYLES = %i[
|
74
|
+
top bot
|
75
|
+
div dash dot3 dot4
|
76
|
+
thick thick_dash thick_dot3 thick_dot4
|
77
|
+
heavy heavy_dash heavy_dot3 heavy_dot4
|
78
|
+
bold bold_dash bold_dot3 bold_dot4
|
79
|
+
double
|
80
|
+
]
|
81
|
+
|
82
|
+
HORIZONTALS = %i[x sx ax bx nx bx_dot3 bx_dot4 bx_dash x_dot3 x_dot4 x_dash]
|
83
|
+
VERTICALS = %i[y yw ye]
|
84
|
+
INTERSECTIONS = %i[nw n ne nd
|
85
|
+
aw ai ae ad au
|
86
|
+
bw bi be bd bu
|
87
|
+
w i e dn up
|
88
|
+
sw s se su]
|
89
|
+
def initialize
|
90
|
+
super
|
91
|
+
@data = {
|
92
|
+
nil => nil,
|
93
|
+
nw: "┌", nx: "─", n: "┬", ne: "┐",
|
94
|
+
yw: "│", y: "│", ye: "│",
|
95
|
+
aw: "╞", ax: "═", ai: "╪", ae: "╡", ad: '╤', au: "╧", # double
|
96
|
+
bw: "┝", bx: "━", bi: "┿", be: "┥", bd: '┯', bu: "┷", # heavy/bold/thick
|
97
|
+
w: "├", x: "─", i: "┼", e: "┤", dn: "┬", up: "┴", # normal div
|
98
|
+
sw: "└", sx: "─", s: "┴", se: "┘",
|
99
|
+
# alternative dots/dashes
|
100
|
+
x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
|
101
|
+
bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
|
102
|
+
}
|
103
|
+
end
|
104
|
+
# Get vertical border elements
|
105
|
+
# @return [Array] 3-element list of [left, center, right]
|
106
|
+
def vertical
|
107
|
+
[maybeleft(:yw), @data[:y], mayberight(:ye)]
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get horizontal border elements
|
111
|
+
# @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
|
112
|
+
def horizontal(type)
|
113
|
+
raise ArgumentError, "Border type is #{type.inspect}, must be one of #{ALLOWED_SEPARATOR_BORDER_STYLES.inspect}" unless ALLOWED_SEPARATOR_BORDER_STYLES.include?(type)
|
114
|
+
lookup = case type
|
115
|
+
when :top
|
116
|
+
[:nw, :nx, :n, :ne, :n, nil]
|
117
|
+
when :bot
|
118
|
+
[:sw, :sx, :s, :se, nil, :s]
|
119
|
+
when :double
|
120
|
+
# typically used for the separator below the heading row or above a footer row)
|
121
|
+
[:aw, :ax, :ai, :ae, :ad, :au]
|
122
|
+
when :thick, :thick_dash, :thick_dot3, :thick_dot4,
|
123
|
+
:heavy, :heavy_dash, :heavy_dot3, :heavy_dot4,
|
124
|
+
:bold, :bold_dash, :bold_dot3, :bold_dot4
|
125
|
+
# alternate thick/bold border
|
126
|
+
xref = type.to_s.sub(/^(thick|heavy|bold)/,'bx').to_sym
|
127
|
+
[:bw, xref, :bi, :be, :bd, :bu]
|
128
|
+
when :dash, :dot3, :dot4
|
129
|
+
# alternate thin dividers
|
130
|
+
xref = "x_#{type}".to_sym
|
131
|
+
[:w, xref, :i, :e, :dn, :up]
|
132
|
+
else # :div (center, non-emphasized)
|
133
|
+
[:w, :x, :i, :e, :dn, :up]
|
134
|
+
end
|
135
|
+
rval = lookup.map { |key| @data.fetch(key) }
|
136
|
+
rval[0] = '' unless @left
|
137
|
+
rval[3] = '' unless @right
|
138
|
+
rval
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Unicode Border With rounded edges
|
143
|
+
class UnicodeRoundBorder < UnicodeBorder
|
144
|
+
def initialize
|
145
|
+
super
|
146
|
+
@data.merge!({nw: '╭', ne: '╮', sw: '╰', se: '╯'})
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Unicode Border with thick outer edges
|
151
|
+
class UnicodeThickEdgeBorder < UnicodeBorder
|
152
|
+
def initialize
|
153
|
+
super
|
154
|
+
@data = {
|
155
|
+
nil => nil,
|
156
|
+
nw: "┏", nx: "━", n: "┯", ne: "┓", nd: nil,
|
157
|
+
yw: "┃", y: "│", ye: "┃",
|
158
|
+
aw: "┣", ax: "═", ai: "╪", ae: "┫", ad: '╤', au: "╧", # double
|
159
|
+
bw: "┣", bx: "━", bi: "┿", be: "┫", bd: '┯', bu: "┷", # heavy/bold/thick
|
160
|
+
w: "┠", x: "─", i: "┼", e: "┨", dn: "┬", up: "┴", # normal div
|
161
|
+
sw: "┗", sx: "━", s: "┷", se: "┛", su: nil,
|
162
|
+
# alternative dots/dashes
|
163
|
+
x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
|
164
|
+
bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
3
169
|
# A Style object holds all the formatting information for a Table object
|
4
170
|
#
|
5
171
|
# To create a table with a certain style, use either the constructor
|
@@ -22,17 +188,51 @@ module Terminal
|
|
22
188
|
# Terminal::Table::Style.defaults = {:width => 80}
|
23
189
|
#
|
24
190
|
class Style
|
191
|
+
extend Forwardable
|
192
|
+
def_delegators :@border, :vertical, :horizontal, :remove_verticals, :remove_horizontals
|
193
|
+
|
25
194
|
@@defaults = {
|
26
|
-
:
|
195
|
+
:border => AsciiBorder.new,
|
27
196
|
:padding_left => 1, :padding_right => 1,
|
28
197
|
:margin_left => '',
|
29
198
|
:width => nil, :alignment => nil,
|
30
|
-
:all_separators => false
|
199
|
+
:all_separators => false,
|
31
200
|
}
|
32
201
|
|
33
|
-
|
34
|
-
|
35
|
-
|
202
|
+
## settors/gettor for legacy ascii borders
|
203
|
+
def border_x=(val) ; @border[:x] = val ; end
|
204
|
+
def border_y=(val) ; @border[:y] = val ; end
|
205
|
+
def border_i=(val) ; @border[:i] = val ; end
|
206
|
+
def border_y ; @border[:y] ; end
|
207
|
+
def border_y_width ; Util::ansi_escape(@border[:y]).length ; end
|
208
|
+
|
209
|
+
# Accessor for instance of Border
|
210
|
+
attr_reader :border
|
211
|
+
def border=(val)
|
212
|
+
if val.is_a? Symbol
|
213
|
+
# convert symbol name like :foo_bar to get class FooBarBorder
|
214
|
+
klass_str = val.to_s.split('_').collect(&:capitalize).join + "Border"
|
215
|
+
begin
|
216
|
+
klass = Terminal::Table::const_get(klass_str)
|
217
|
+
@border = klass.new
|
218
|
+
rescue NameError
|
219
|
+
raise "Cannot lookup class Terminal::Table::#{klass_str} from symbol #{val.inspect}"
|
220
|
+
end
|
221
|
+
else
|
222
|
+
@border = val
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def border_top=(val) ; @border.top = val ; end
|
227
|
+
def border_bottom=(val) ; @border.bottom = val ; end
|
228
|
+
def border_left=(val) ; @border.left = val ; end
|
229
|
+
def border_right=(val) ; @border.right = val ; end
|
230
|
+
|
231
|
+
def border_top ; @border.top ; end
|
232
|
+
def border_bottom ; @border.bottom ; end
|
233
|
+
def border_left ; @border.left ; end
|
234
|
+
def border_right ; @border.right ; end
|
235
|
+
|
36
236
|
|
37
237
|
attr_accessor :padding_left
|
38
238
|
attr_accessor :padding_right
|
@@ -44,23 +244,30 @@ module Terminal
|
|
44
244
|
|
45
245
|
attr_accessor :all_separators
|
46
246
|
|
47
|
-
|
247
|
+
|
48
248
|
def initialize options = {}
|
49
249
|
apply self.class.defaults.merge(options)
|
50
250
|
end
|
51
251
|
|
52
252
|
def apply options
|
53
|
-
options.each
|
253
|
+
options.each do |m, v|
|
254
|
+
__send__ "#{m}=", v
|
255
|
+
end
|
54
256
|
end
|
55
|
-
|
257
|
+
|
56
258
|
class << self
|
57
259
|
def defaults
|
58
|
-
@@defaults
|
260
|
+
klass_defaults = @@defaults.dup
|
261
|
+
# border is an object that needs to be duplicated on instantiation,
|
262
|
+
# otherwise everything will be referencing the same object-id.
|
263
|
+
klass_defaults[:border] = klass_defaults[:border].dup
|
264
|
+
klass_defaults
|
59
265
|
end
|
60
|
-
|
266
|
+
|
61
267
|
def defaults= options
|
62
268
|
@@defaults = defaults.merge(options)
|
63
269
|
end
|
270
|
+
|
64
271
|
end
|
65
272
|
|
66
273
|
def on_change attr
|
@@ -71,6 +278,7 @@ module Terminal
|
|
71
278
|
yield attr.to_sym, value
|
72
279
|
end
|
73
280
|
end
|
281
|
+
|
74
282
|
end
|
75
283
|
end
|
76
284
|
end
|
data/lib/terminal-table/table.rb
CHANGED
@@ -7,9 +7,10 @@ module Terminal
|
|
7
7
|
attr_reader :headings
|
8
8
|
|
9
9
|
##
|
10
|
-
# Generates a ASCII table with the given _options_.
|
10
|
+
# Generates a ASCII/Unicode table with the given _options_.
|
11
11
|
|
12
12
|
def initialize options = {}, &block
|
13
|
+
@elaborated = false
|
13
14
|
@headings = []
|
14
15
|
@rows = []
|
15
16
|
@column_widths = []
|
@@ -29,6 +30,7 @@ module Terminal
|
|
29
30
|
r = rows
|
30
31
|
column(n).each_with_index do |col, i|
|
31
32
|
cell = r[i][n]
|
33
|
+
next unless cell
|
32
34
|
cell.alignment = alignment unless cell.alignment?
|
33
35
|
end
|
34
36
|
end
|
@@ -46,12 +48,12 @@ module Terminal
|
|
46
48
|
##
|
47
49
|
# Add a separator.
|
48
50
|
|
49
|
-
def add_separator
|
50
|
-
|
51
|
+
def add_separator(border_type: :div)
|
52
|
+
@rows << Separator.new(self, border_type: border_type)
|
51
53
|
end
|
52
54
|
|
53
55
|
def cell_spacing
|
54
|
-
cell_padding + style.
|
56
|
+
cell_padding + style.border_y_width
|
55
57
|
end
|
56
58
|
|
57
59
|
def cell_padding
|
@@ -94,7 +96,7 @@ module Terminal
|
|
94
96
|
# Return length of column _n_.
|
95
97
|
|
96
98
|
def column_width n
|
97
|
-
|
99
|
+
column_widths[n] || 0
|
98
100
|
end
|
99
101
|
alias length_of_column column_width # for legacy support
|
100
102
|
|
@@ -118,28 +120,57 @@ module Terminal
|
|
118
120
|
end
|
119
121
|
|
120
122
|
##
|
121
|
-
#
|
123
|
+
# Elaborate rows to form an Array of Rows and Separators with adjacency properties added.
|
124
|
+
#
|
125
|
+
# This is separated from the String rendering so that certain features may be tweaked
|
126
|
+
# before the String is built.
|
122
127
|
|
123
|
-
def
|
124
|
-
|
125
|
-
buffer = [
|
128
|
+
def elaborate_rows
|
129
|
+
|
130
|
+
buffer = style.border_top ? [Separator.new(self, border_type: :top, implicit: true)] : []
|
126
131
|
unless @title.nil?
|
127
132
|
buffer << Row.new(self, [title_cell_options])
|
128
|
-
buffer <<
|
133
|
+
buffer << Separator.new(self, implicit: true)
|
129
134
|
end
|
130
135
|
@headings.each do |row|
|
131
136
|
unless row.cells.empty?
|
132
137
|
buffer << row
|
133
|
-
buffer <<
|
138
|
+
buffer << Separator.new(self, border_type: :double, implicit: true)
|
134
139
|
end
|
135
140
|
end
|
136
141
|
if style.all_separators
|
137
|
-
|
142
|
+
@rows.each_with_index do |row, idx|
|
143
|
+
# last separator is bottom, others are :div
|
144
|
+
border_type = (idx == @rows.size - 1) ? :bot : :div
|
145
|
+
buffer << row
|
146
|
+
buffer << Separator.new(self, border_type: border_type, implicit: true)
|
147
|
+
end
|
138
148
|
else
|
139
149
|
buffer += @rows
|
140
|
-
buffer <<
|
150
|
+
buffer << Separator.new(self, border_type: :bot, implicit: true) if style.border_bottom
|
151
|
+
end
|
152
|
+
|
153
|
+
# After all implicit Separators are inserted we need to save off the
|
154
|
+
# adjacent rows so that we can decide what type of intersections to use
|
155
|
+
# based on column spans in the adjacent row(s).
|
156
|
+
buffer.each_with_index do |r, idx|
|
157
|
+
if r.is_a?(Separator)
|
158
|
+
prev_row = idx > 0 ? buffer[idx - 1] : nil
|
159
|
+
next_row = buffer.fetch(idx + 1, nil)
|
160
|
+
r.save_adjacent_rows(prev_row, next_row)
|
161
|
+
end
|
141
162
|
end
|
142
|
-
|
163
|
+
|
164
|
+
@elaborated = true
|
165
|
+
@rows = buffer
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Render the table.
|
170
|
+
|
171
|
+
def render
|
172
|
+
elaborate_rows unless @elaborated
|
173
|
+
@rows.map { |r| style.margin_left + r.render.rstrip }.join("\n")
|
143
174
|
end
|
144
175
|
alias :to_s :render
|
145
176
|
|
@@ -181,7 +212,7 @@ module Terminal
|
|
181
212
|
private
|
182
213
|
|
183
214
|
def columns_width
|
184
|
-
column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.
|
215
|
+
column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y_width
|
185
216
|
end
|
186
217
|
|
187
218
|
def recalc_column_widths
|
@@ -245,11 +276,11 @@ module Terminal
|
|
245
276
|
end
|
246
277
|
|
247
278
|
# sort reversely and store it back.
|
248
|
-
dp[colspan][index] = rmap.sort.reverse
|
279
|
+
dp[colspan][index] = Hash[rmap.sort.reverse]
|
249
280
|
end
|
250
281
|
end
|
251
282
|
|
252
|
-
resolve =
|
283
|
+
resolve = lambda do |colspan, full_width, index = 0|
|
253
284
|
# stop if reaches the bottom level.
|
254
285
|
return @column_widths[index] = full_width if colspan == 1
|
255
286
|
|
@@ -300,7 +331,7 @@ module Terminal
|
|
300
331
|
|
301
332
|
full_width = dp[n_cols][0].keys.first
|
302
333
|
unless style.width.nil?
|
303
|
-
new_width = style.width - space_width - style.
|
334
|
+
new_width = style.width - space_width - style.border_y_width
|
304
335
|
if new_width < full_width
|
305
336
|
raise "Table width exceeds wanted width " +
|
306
337
|
"of #{style.width} characters."
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Terminal
|
2
|
+
class Table
|
3
|
+
module Util
|
4
|
+
# removes all ANSI escape sequences (e.g. color)
|
5
|
+
def ansi_escape(line)
|
6
|
+
line.to_s.gsub(/\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]/, '').
|
7
|
+
gsub(/\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]/, '').
|
8
|
+
gsub(/(\x03|\x1a)/, '')
|
9
|
+
end
|
10
|
+
module_function :ansi_escape
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/terminal-table.gemspec
CHANGED
@@ -16,11 +16,11 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_development_dependency "bundler", "~>
|
20
|
-
spec.add_development_dependency "rake", "~>
|
19
|
+
spec.add_development_dependency "bundler", "~> 2"
|
20
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
21
21
|
spec.add_development_dependency "rspec", ">= 3.0"
|
22
22
|
spec.add_development_dependency "term-ansicolor"
|
23
23
|
spec.add_development_dependency "pry"
|
24
24
|
|
25
|
-
spec.add_runtime_dependency "unicode-display_width", "
|
25
|
+
spec.add_runtime_dependency "unicode-display_width", [">= 1.1.1", "< 3"]
|
26
26
|
end
|