terminal-table 1.7.1 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe53fefe455edfe656cec0324c7303976548eab6
4
- data.tar.gz: 2b52d6330087eb91537b8180d2162a4321865df0
3
+ metadata.gz: 6c0927620bf05c372458c73382bfd171be972288
4
+ data.tar.gz: e7aad54b23b37641a9edf68867d8b741af52563e
5
5
  SHA512:
6
- metadata.gz: e3a220c191de479bd21f661d583911c3cedaa539e9d5036df754d866c7d2a6b069f2a37f1b7b424bcc1c13cc0cc6cf6aacc297b72b55ba9abfc89ef7ecf41c5b
7
- data.tar.gz: 2eb2846a2e1a0e448bb87bc0f0f6c6701338137a7c82cb5642bbb5040060e99a83251d440c4afb0ffb2a746d3f4aec36d9b5f250091b24ea0a07aac5ca2ff0aa
6
+ metadata.gz: 469f18cf16097c03b06e4e39696f04b8fa7547facc206cdfc52ac4fa918b78109d6ef6caeb14366c3bc2d4902ef3e6fd23587d453c90d80fabd7249a94b1c0ec
7
+ data.tar.gz: d6c9253df10b4bed36b4a494ef20c39dc462e2c388ad3b1cdeb464676bbe31458b718f14283818c99713e813c5c9cd728ddd7ce8f4bd64cebdba8f300633b689
@@ -1,3 +1,8 @@
1
+ 1.7.2 / 2016-09-09
2
+ ==================
3
+
4
+ * Fix packing table to a minimal width (@vizv, #76)
5
+
1
6
  1.7.1 / 2016-08-29
2
7
  ==================
3
8
 
@@ -43,6 +43,10 @@ module Terminal
43
43
  end.join(y) + y
44
44
  end.join("\n")
45
45
  end
46
+
47
+ def number_of_columns
48
+ @cells.collect(&:colspan).inject(0, &:+)
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -62,6 +62,15 @@ module Terminal
62
62
  @@defaults = defaults.merge(options)
63
63
  end
64
64
  end
65
+
66
+ def on_change attr
67
+ method_name = :"#{attr}="
68
+ old_method = method method_name
69
+ define_singleton_method(method_name) do |value|
70
+ old_method.call value
71
+ yield attr.to_sym, value
72
+ end
73
+ end
65
74
  end
66
75
  end
67
76
  end
@@ -10,12 +10,16 @@ module Terminal
10
10
  # Generates a ASCII table with the given _options_.
11
11
 
12
12
  def initialize options = {}, &block
13
+ @headings = []
14
+ @rows = []
13
15
  @column_widths = []
14
16
  self.style = options.fetch :style, {}
15
17
  self.headings = options.fetch :headings, []
16
18
  self.rows = options.fetch :rows, []
17
19
  self.title = options.fetch :title, nil
18
20
  yield_or_eval(&block) if block
21
+
22
+ style.on_change(:width) { require_column_widths_recalc }
19
23
  end
20
24
 
21
25
  ##
@@ -35,7 +39,7 @@ module Terminal
35
39
  def add_row array
36
40
  row = array == :separator ? Separator.new(self) : Row.new(self, array)
37
41
  @rows << row
38
- recalc_column_widths row
42
+ require_column_widths_recalc unless row.is_a?(Separator)
39
43
  end
40
44
  alias :<< :add_row
41
45
 
@@ -90,8 +94,7 @@ module Terminal
90
94
  # Return length of column _n_.
91
95
 
92
96
  def column_width n
93
- width = @column_widths[n] || 0
94
- width + additional_column_widths[n].to_i
97
+ width = column_widths[n] || 0
95
98
  end
96
99
  alias length_of_column column_width # for legacy support
97
100
 
@@ -99,7 +102,7 @@ module Terminal
99
102
  # Return total number of columns available.
100
103
 
101
104
  def number_of_columns
102
- headings_with_rows.map { |r| r.cells.size }.max
105
+ headings_with_rows.map { |r| r.number_of_columns }.max || 0
103
106
  end
104
107
 
105
108
  ##
@@ -109,7 +112,7 @@ module Terminal
109
112
  arrays = [arrays] unless arrays.first.is_a?(Array)
110
113
  @headings = arrays.map do |array|
111
114
  row = Row.new(self, array)
112
- recalc_column_widths row
115
+ require_column_widths_recalc
113
116
  row
114
117
  end
115
118
  end
@@ -162,7 +165,7 @@ module Terminal
162
165
 
163
166
  def title=(title)
164
167
  @title = title
165
- recalc_column_widths Row.new(self, [title_cell_options])
168
+ require_column_widths_recalc
166
169
  end
167
170
 
168
171
  ##
@@ -178,42 +181,134 @@ module Terminal
178
181
  private
179
182
 
180
183
  def columns_width
181
- @column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y.length
184
+ column_widths.inject(0) { |s, i| s + i + cell_spacing } + style.border_y.length
182
185
  end
183
186
 
184
- def additional_column_widths
185
- return [] if style.width.nil?
186
- spacing = style.width - columns_width
187
- if spacing < 0
188
- raise "Table width exceeds wanted width of #{style.width} characters."
189
- else
190
- per_col = spacing / number_of_columns
191
- arr = (1...number_of_columns).to_a.map { |i| per_col }
192
- other_cols = arr.inject(0) { |s, i| s + i }
193
- arr << spacing - other_cols
194
- arr
187
+ def recalc_column_widths
188
+ @require_column_widths_recalc = false
189
+ n_cols = number_of_columns
190
+ space_width = cell_spacing
191
+ return if n_cols == 0
192
+
193
+ # prepare rows
194
+ all_rows = headings_with_rows
195
+ all_rows << Row.new(self, [title_cell_options]) unless @title.nil?
196
+
197
+ # DP states, dp[colspan][index][split_offset] => column_width.
198
+ dp = []
199
+
200
+ # prepare initial value for DP.
201
+ all_rows.each do |row|
202
+ index = 0
203
+ row.cells.each do |cell|
204
+ cell_value = cell.value_for_column_width_recalc
205
+ cell_width = Unicode::DisplayWidth.of(cell_value.to_s)
206
+ colspan = cell.colspan
207
+
208
+ # find column width from each single cell.
209
+ dp[colspan] ||= []
210
+ dp[colspan][index] ||= [0] # add a fake cell with length 0.
211
+ dp[colspan][index][colspan] ||= 0 # initialize column length to 0.
212
+
213
+ # the last index `colspan` means width of the single column (split
214
+ # at end of each column), not a width made up of multiple columns.
215
+ single_column_length = [cell_width, dp[colspan][index][colspan]].max
216
+ dp[colspan][index][colspan] = single_column_length
217
+
218
+ index += colspan
219
+ end
195
220
  end
196
- end
197
221
 
198
- def recalc_column_widths row
199
- return if row.is_a? Separator
200
- i = 0
201
- row.cells.each do |cell|
202
- colspan = cell.colspan
203
- cell_value = cell.value_for_column_width_recalc
204
- colspan.downto(1) do |j|
205
- cell_length = Unicode::DisplayWidth.of(cell_value.to_s)
206
- if colspan > 1
207
- spacing_length = cell_spacing * (colspan - 1)
208
- length_in_columns = (cell_length - spacing_length)
209
- cell_length = (length_in_columns.to_f / colspan).ceil
222
+ # run DP.
223
+ (1..n_cols).each do |colspan|
224
+ dp[colspan] ||= []
225
+ (0..n_cols-colspan).each do |index|
226
+ dp[colspan][index] ||= [1]
227
+ (1...colspan).each do |offset|
228
+ # processed level became reverse map from width => [offset, ...].
229
+ left_colspan = offset
230
+ left_index = index
231
+ left_width = dp[left_colspan][left_index].keys.first
232
+
233
+ right_colspan = colspan - left_colspan
234
+ right_index = index + offset
235
+ right_width = dp[right_colspan][right_index].keys.first
236
+
237
+ dp[colspan][index][offset] = left_width + right_width + space_width
210
238
  end
211
- if @column_widths[i].to_i < cell_length
212
- @column_widths[i] = cell_length
239
+
240
+ # reverse map it for resolution (max width and short offset first).
241
+ rmap = {}
242
+ dp[colspan][index].each_with_index do |width, offset|
243
+ rmap[width] ||= []
244
+ rmap[width] << offset
213
245
  end
214
- i = i + 1
246
+
247
+ # sort reversely and store it back.
248
+ dp[colspan][index] = rmap.sort.reverse.to_h
215
249
  end
216
250
  end
251
+
252
+ resolve = -> (colspan, full_width, index = 0) do
253
+ # stop if reaches the bottom level.
254
+ return @column_widths[index] = full_width if colspan == 1
255
+
256
+ # choose best split offset for partition, or second best result
257
+ # if first one is not dividable.
258
+ candidate_offsets = dp[colspan][index].collect(&:last).flatten
259
+ offset = candidate_offsets[0]
260
+ offset = candidate_offsets[1] if offset == colspan
261
+
262
+ # prepare for next round.
263
+ left_colspan = offset
264
+ left_index = index
265
+ left_width = dp[left_colspan][left_index].keys.first
266
+
267
+ right_colspan = colspan - left_colspan
268
+ right_index = index + offset
269
+ right_width = dp[right_colspan][right_index].keys.first
270
+
271
+ # calculate reference column width, give remaining spaces to left.
272
+ total_non_space_width = full_width - (colspan - 1) * space_width
273
+ ref_column_width = total_non_space_width / colspan
274
+ remainder = total_non_space_width % colspan
275
+ rem_left_width = [remainder, left_colspan].min
276
+ rem_right_width = remainder - rem_left_width
277
+ ref_left_width = ref_column_width * left_colspan +
278
+ (left_colspan - 1) * space_width + rem_left_width
279
+ ref_right_width = ref_column_width * right_colspan +
280
+ (right_colspan - 1) * space_width + rem_right_width
281
+
282
+ # at most one width can be greater than the reference width.
283
+ if left_width <= ref_left_width and right_width <= ref_right_width
284
+ # use refernce width (evenly partition).
285
+ left_width = ref_left_width
286
+ right_width = ref_right_width
287
+ else
288
+ # the wider one takes its value, shorter one takes the rest.
289
+ if left_width > ref_left_width
290
+ right_width = full_width - left_width - space_width
291
+ else
292
+ left_width = full_width - right_width - space_width
293
+ end
294
+ end
295
+
296
+ # run next round.
297
+ resolve.call(left_colspan, left_width, left_index)
298
+ resolve.call(right_colspan, right_width, right_index)
299
+ end
300
+
301
+ full_width = dp[n_cols][0].keys.first
302
+ unless style.width.nil?
303
+ new_width = style.width - space_width - style.border_y.length
304
+ if new_width < full_width
305
+ raise "Table width exceeds wanted width " +
306
+ "of #{style.width} characters."
307
+ end
308
+ full_width = new_width
309
+ end
310
+
311
+ resolve.call(n_cols, full_width)
217
312
  end
218
313
 
219
314
  ##
@@ -235,5 +330,14 @@ module Terminal
235
330
  def title_cell_options
236
331
  {:value => @title, :alignment => :center, :colspan => number_of_columns}
237
332
  end
333
+
334
+ def require_column_widths_recalc
335
+ @require_column_widths_recalc = true
336
+ end
337
+
338
+ def column_widths
339
+ recalc_column_widths if @require_column_widths_recalc
340
+ @column_widths
341
+ end
238
342
  end
239
343
  end
@@ -1,5 +1,5 @@
1
1
  module Terminal
2
2
  class Table
3
- VERSION = '1.7.1'
3
+ VERSION = '1.7.2'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminal-table
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - TJ Holowaychuk
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-09-07 00:00:00.000000000 Z
12
+ date: 2016-09-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler