terminal-table 1.7.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|