terminal-table 1.7.1 → 1.7.2

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 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