terminal-table 1.7.1 → 3.0.0
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 +64 -0
- data/LICENSE.txt +21 -0
- data/README.md +391 -0
- data/Rakefile +10 -4
- 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/issue95.rb +42 -0
- data/examples/strong_separator.rb +23 -0
- data/lib/terminal-table.rb +2 -2
- data/lib/terminal-table/cell.rb +3 -11
- data/lib/terminal-table/row.rb +21 -3
- data/lib/terminal-table/separator.rb +56 -4
- data/lib/terminal-table/style.rb +227 -10
- data/lib/terminal-table/table.rb +183 -48
- data/lib/terminal-table/util.rb +13 -0
- data/lib/terminal-table/version.rb +1 -1
- data/terminal-table.gemspec +3 -3
- metadata +27 -13
- data/README.rdoc +0 -238
data/lib/terminal-table/table.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'unicode/display_width'
|
1
|
+
require 'unicode/display_width/no_string_ext'
|
2
2
|
|
3
3
|
module Terminal
|
4
4
|
class Table
|
@@ -7,15 +7,20 @@ 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
|
14
|
+
@headings = []
|
15
|
+
@rows = []
|
13
16
|
@column_widths = []
|
14
17
|
self.style = options.fetch :style, {}
|
15
18
|
self.headings = options.fetch :headings, []
|
16
19
|
self.rows = options.fetch :rows, []
|
17
20
|
self.title = options.fetch :title, nil
|
18
21
|
yield_or_eval(&block) if block
|
22
|
+
|
23
|
+
style.on_change(:width) { require_column_widths_recalc }
|
19
24
|
end
|
20
25
|
|
21
26
|
##
|
@@ -25,6 +30,7 @@ module Terminal
|
|
25
30
|
r = rows
|
26
31
|
column(n).each_with_index do |col, i|
|
27
32
|
cell = r[i][n]
|
33
|
+
next unless cell
|
28
34
|
cell.alignment = alignment unless cell.alignment?
|
29
35
|
end
|
30
36
|
end
|
@@ -35,19 +41,19 @@ module Terminal
|
|
35
41
|
def add_row array
|
36
42
|
row = array == :separator ? Separator.new(self) : Row.new(self, array)
|
37
43
|
@rows << row
|
38
|
-
|
44
|
+
require_column_widths_recalc unless row.is_a?(Separator)
|
39
45
|
end
|
40
46
|
alias :<< :add_row
|
41
47
|
|
42
48
|
##
|
43
49
|
# Add a separator.
|
44
50
|
|
45
|
-
def add_separator
|
46
|
-
|
51
|
+
def add_separator(border_type: :div)
|
52
|
+
@rows << Separator.new(self, border_type: border_type)
|
47
53
|
end
|
48
54
|
|
49
55
|
def cell_spacing
|
50
|
-
cell_padding + style.
|
56
|
+
cell_padding + style.border_y_width
|
51
57
|
end
|
52
58
|
|
53
59
|
def cell_padding
|
@@ -90,8 +96,7 @@ module Terminal
|
|
90
96
|
# Return length of column _n_.
|
91
97
|
|
92
98
|
def column_width n
|
93
|
-
|
94
|
-
width + additional_column_widths[n].to_i
|
99
|
+
column_widths[n] || 0
|
95
100
|
end
|
96
101
|
alias length_of_column column_width # for legacy support
|
97
102
|
|
@@ -99,7 +104,7 @@ module Terminal
|
|
99
104
|
# Return total number of columns available.
|
100
105
|
|
101
106
|
def number_of_columns
|
102
|
-
headings_with_rows.map { |r| r.
|
107
|
+
headings_with_rows.map { |r| r.number_of_columns }.max || 0
|
103
108
|
end
|
104
109
|
|
105
110
|
##
|
@@ -109,34 +114,63 @@ module Terminal
|
|
109
114
|
arrays = [arrays] unless arrays.first.is_a?(Array)
|
110
115
|
@headings = arrays.map do |array|
|
111
116
|
row = Row.new(self, array)
|
112
|
-
|
117
|
+
require_column_widths_recalc
|
113
118
|
row
|
114
119
|
end
|
115
120
|
end
|
116
121
|
|
117
122
|
##
|
118
|
-
#
|
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.
|
119
127
|
|
120
|
-
def
|
121
|
-
|
122
|
-
buffer = [
|
128
|
+
def elaborate_rows
|
129
|
+
|
130
|
+
buffer = style.border_top ? [Separator.new(self, border_type: :top, implicit: true)] : []
|
123
131
|
unless @title.nil?
|
124
132
|
buffer << Row.new(self, [title_cell_options])
|
125
|
-
buffer <<
|
133
|
+
buffer << Separator.new(self, implicit: true)
|
126
134
|
end
|
127
135
|
@headings.each do |row|
|
128
136
|
unless row.cells.empty?
|
129
137
|
buffer << row
|
130
|
-
buffer <<
|
138
|
+
buffer << Separator.new(self, border_type: :double, implicit: true)
|
131
139
|
end
|
132
140
|
end
|
133
141
|
if style.all_separators
|
134
|
-
|
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
|
135
148
|
else
|
136
149
|
buffer += @rows
|
137
|
-
buffer <<
|
150
|
+
buffer << Separator.new(self, border_type: :bot, implicit: true) if style.border_bottom
|
138
151
|
end
|
139
|
-
|
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
|
162
|
+
end
|
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")
|
140
174
|
end
|
141
175
|
alias :to_s :render
|
142
176
|
|
@@ -162,7 +196,7 @@ module Terminal
|
|
162
196
|
|
163
197
|
def title=(title)
|
164
198
|
@title = title
|
165
|
-
|
199
|
+
require_column_widths_recalc
|
166
200
|
end
|
167
201
|
|
168
202
|
##
|
@@ -178,42 +212,134 @@ module Terminal
|
|
178
212
|
private
|
179
213
|
|
180
214
|
def columns_width
|
181
|
-
|
215
|
+
column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y_width
|
182
216
|
end
|
183
217
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
218
|
+
def recalc_column_widths
|
219
|
+
@require_column_widths_recalc = false
|
220
|
+
n_cols = number_of_columns
|
221
|
+
space_width = cell_spacing
|
222
|
+
return if n_cols == 0
|
223
|
+
|
224
|
+
# prepare rows
|
225
|
+
all_rows = headings_with_rows
|
226
|
+
all_rows << Row.new(self, [title_cell_options]) unless @title.nil?
|
227
|
+
|
228
|
+
# DP states, dp[colspan][index][split_offset] => column_width.
|
229
|
+
dp = []
|
230
|
+
|
231
|
+
# prepare initial value for DP.
|
232
|
+
all_rows.each do |row|
|
233
|
+
index = 0
|
234
|
+
row.cells.each do |cell|
|
235
|
+
cell_value = cell.value_for_column_width_recalc
|
236
|
+
cell_width = Unicode::DisplayWidth.of(cell_value.to_s)
|
237
|
+
colspan = cell.colspan
|
238
|
+
|
239
|
+
# find column width from each single cell.
|
240
|
+
dp[colspan] ||= []
|
241
|
+
dp[colspan][index] ||= [0] # add a fake cell with length 0.
|
242
|
+
dp[colspan][index][colspan] ||= 0 # initialize column length to 0.
|
243
|
+
|
244
|
+
# the last index `colspan` means width of the single column (split
|
245
|
+
# at end of each column), not a width made up of multiple columns.
|
246
|
+
single_column_length = [cell_width, dp[colspan][index][colspan]].max
|
247
|
+
dp[colspan][index][colspan] = single_column_length
|
248
|
+
|
249
|
+
index += colspan
|
250
|
+
end
|
195
251
|
end
|
196
|
-
end
|
197
252
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
253
|
+
# run DP.
|
254
|
+
(1..n_cols).each do |colspan|
|
255
|
+
dp[colspan] ||= []
|
256
|
+
(0..n_cols-colspan).each do |index|
|
257
|
+
dp[colspan][index] ||= [1]
|
258
|
+
(1...colspan).each do |offset|
|
259
|
+
# processed level became reverse map from width => [offset, ...].
|
260
|
+
left_colspan = offset
|
261
|
+
left_index = index
|
262
|
+
left_width = dp[left_colspan][left_index].keys.first
|
263
|
+
|
264
|
+
right_colspan = colspan - left_colspan
|
265
|
+
right_index = index + offset
|
266
|
+
right_width = dp[right_colspan][right_index].keys.first
|
267
|
+
|
268
|
+
dp[colspan][index][offset] = left_width + right_width + space_width
|
210
269
|
end
|
211
|
-
|
212
|
-
|
270
|
+
|
271
|
+
# reverse map it for resolution (max width and short offset first).
|
272
|
+
rmap = {}
|
273
|
+
dp[colspan][index].each_with_index do |width, offset|
|
274
|
+
rmap[width] ||= []
|
275
|
+
rmap[width] << offset
|
276
|
+
end
|
277
|
+
|
278
|
+
# sort reversely and store it back.
|
279
|
+
dp[colspan][index] = Hash[rmap.sort.reverse]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
resolve = lambda do |colspan, full_width, index = 0|
|
284
|
+
# stop if reaches the bottom level.
|
285
|
+
return @column_widths[index] = full_width if colspan == 1
|
286
|
+
|
287
|
+
# choose best split offset for partition, or second best result
|
288
|
+
# if first one is not dividable.
|
289
|
+
candidate_offsets = dp[colspan][index].collect(&:last).flatten
|
290
|
+
offset = candidate_offsets[0]
|
291
|
+
offset = candidate_offsets[1] if offset == colspan
|
292
|
+
|
293
|
+
# prepare for next round.
|
294
|
+
left_colspan = offset
|
295
|
+
left_index = index
|
296
|
+
left_width = dp[left_colspan][left_index].keys.first
|
297
|
+
|
298
|
+
right_colspan = colspan - left_colspan
|
299
|
+
right_index = index + offset
|
300
|
+
right_width = dp[right_colspan][right_index].keys.first
|
301
|
+
|
302
|
+
# calculate reference column width, give remaining spaces to left.
|
303
|
+
total_non_space_width = full_width - (colspan - 1) * space_width
|
304
|
+
ref_column_width = total_non_space_width / colspan
|
305
|
+
remainder = total_non_space_width % colspan
|
306
|
+
rem_left_width = [remainder, left_colspan].min
|
307
|
+
rem_right_width = remainder - rem_left_width
|
308
|
+
ref_left_width = ref_column_width * left_colspan +
|
309
|
+
(left_colspan - 1) * space_width + rem_left_width
|
310
|
+
ref_right_width = ref_column_width * right_colspan +
|
311
|
+
(right_colspan - 1) * space_width + rem_right_width
|
312
|
+
|
313
|
+
# at most one width can be greater than the reference width.
|
314
|
+
if left_width <= ref_left_width and right_width <= ref_right_width
|
315
|
+
# use refernce width (evenly partition).
|
316
|
+
left_width = ref_left_width
|
317
|
+
right_width = ref_right_width
|
318
|
+
else
|
319
|
+
# the wider one takes its value, shorter one takes the rest.
|
320
|
+
if left_width > ref_left_width
|
321
|
+
right_width = full_width - left_width - space_width
|
322
|
+
else
|
323
|
+
left_width = full_width - right_width - space_width
|
213
324
|
end
|
214
|
-
i = i + 1
|
215
325
|
end
|
326
|
+
|
327
|
+
# run next round.
|
328
|
+
resolve.call(left_colspan, left_width, left_index)
|
329
|
+
resolve.call(right_colspan, right_width, right_index)
|
330
|
+
end
|
331
|
+
|
332
|
+
full_width = dp[n_cols][0].keys.first
|
333
|
+
unless style.width.nil?
|
334
|
+
new_width = style.width - space_width - style.border_y_width
|
335
|
+
if new_width < full_width
|
336
|
+
raise "Table width exceeds wanted width " +
|
337
|
+
"of #{style.width} characters."
|
338
|
+
end
|
339
|
+
full_width = new_width
|
216
340
|
end
|
341
|
+
|
342
|
+
resolve.call(n_cols, full_width)
|
217
343
|
end
|
218
344
|
|
219
345
|
##
|
@@ -235,5 +361,14 @@ module Terminal
|
|
235
361
|
def title_cell_options
|
236
362
|
{:value => @title, :alignment => :center, :colspan => number_of_columns}
|
237
363
|
end
|
364
|
+
|
365
|
+
def require_column_widths_recalc
|
366
|
+
@require_column_widths_recalc = true
|
367
|
+
end
|
368
|
+
|
369
|
+
def column_widths
|
370
|
+
recalc_column_widths if @require_column_widths_recalc
|
371
|
+
@column_widths
|
372
|
+
end
|
238
373
|
end
|
239
374
|
end
|
@@ -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", "~> 1.1.1"
|
25
|
+
spec.add_runtime_dependency "unicode-display_width", ["~> 1.1", ">= 1.1.1"]
|
26
26
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terminal-table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TJ Holowaychuk
|
8
8
|
- Scott J. Goldman
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-01-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -17,28 +17,28 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '2'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '2'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: rake
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '13.0'
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
41
|
+
version: '13.0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: rspec
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,6 +86,9 @@ dependencies:
|
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.1'
|
91
|
+
- - ">="
|
89
92
|
- !ruby/object:Gem::Version
|
90
93
|
version: 1.1.1
|
91
94
|
type: :runtime
|
@@ -93,23 +96,34 @@ dependencies:
|
|
93
96
|
version_requirements: !ruby/object:Gem::Requirement
|
94
97
|
requirements:
|
95
98
|
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '1.1'
|
101
|
+
- - ">="
|
96
102
|
- !ruby/object:Gem::Version
|
97
103
|
version: 1.1.1
|
98
|
-
description:
|
104
|
+
description:
|
99
105
|
email:
|
100
106
|
- tj@vision-media.ca
|
101
107
|
executables: []
|
102
108
|
extensions: []
|
103
109
|
extra_rdoc_files: []
|
104
110
|
files:
|
111
|
+
- ".github/workflows/ci.yml"
|
105
112
|
- ".gitignore"
|
106
113
|
- Gemfile
|
114
|
+
- Gemfile.lock
|
107
115
|
- History.rdoc
|
116
|
+
- LICENSE.txt
|
108
117
|
- Manifest
|
109
|
-
- README.
|
118
|
+
- README.md
|
110
119
|
- Rakefile
|
111
120
|
- Todo.rdoc
|
112
121
|
- examples/examples.rb
|
122
|
+
- examples/examples_unicode.rb
|
123
|
+
- examples/issue100.rb
|
124
|
+
- examples/issue111.rb
|
125
|
+
- examples/issue95.rb
|
126
|
+
- examples/strong_separator.rb
|
113
127
|
- lib/terminal-table.rb
|
114
128
|
- lib/terminal-table/cell.rb
|
115
129
|
- lib/terminal-table/import.rb
|
@@ -118,13 +132,14 @@ files:
|
|
118
132
|
- lib/terminal-table/style.rb
|
119
133
|
- lib/terminal-table/table.rb
|
120
134
|
- lib/terminal-table/table_helper.rb
|
135
|
+
- lib/terminal-table/util.rb
|
121
136
|
- lib/terminal-table/version.rb
|
122
137
|
- terminal-table.gemspec
|
123
138
|
homepage: https://github.com/tj/terminal-table
|
124
139
|
licenses:
|
125
140
|
- MIT
|
126
141
|
metadata: {}
|
127
|
-
post_install_message:
|
142
|
+
post_install_message:
|
128
143
|
rdoc_options: []
|
129
144
|
require_paths:
|
130
145
|
- lib
|
@@ -139,9 +154,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
154
|
- !ruby/object:Gem::Version
|
140
155
|
version: '0'
|
141
156
|
requirements: []
|
142
|
-
|
143
|
-
|
144
|
-
signing_key:
|
157
|
+
rubygems_version: 3.1.4
|
158
|
+
signing_key:
|
145
159
|
specification_version: 4
|
146
160
|
summary: Simple, feature rich ascii table generation library
|
147
161
|
test_files: []
|