hirb 0.2.9 → 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +13 -0
- data/LICENSE.txt +1 -1
- data/README.rdoc +1 -1
- data/VERSION.yml +3 -2
- data/lib/hirb.rb +22 -9
- data/lib/hirb/formatter.rb +1 -5
- data/lib/hirb/helpers/active_record_table.rb +1 -1
- data/lib/hirb/helpers/object_table.rb +2 -3
- data/lib/hirb/helpers/table.rb +104 -90
- data/lib/hirb/helpers/table/filters.rb +10 -0
- data/lib/hirb/helpers/table/resizer.rb +82 -0
- data/lib/hirb/helpers/vertical_table.rb +12 -6
- data/lib/hirb/menu.rb +204 -36
- data/lib/hirb/util.rb +11 -2
- data/lib/hirb/view.rb +29 -10
- data/test/auto_table_test.rb +8 -23
- data/test/formatter_test.rb +2 -2
- data/test/hirb_test.rb +13 -4
- data/test/menu_test.rb +134 -5
- data/test/object_table_test.rb +23 -4
- data/test/pager_test.rb +1 -1
- data/test/resizer_test.rb +61 -0
- data/test/table_test.rb +182 -75
- data/test/util_test.rb +5 -0
- data/test/view_test.rb +36 -4
- metadata +6 -2
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
== 0.2.10
|
2
|
+
* Added multiple options to Menu, most importantly :two_d and :action.
|
3
|
+
* Improved table resizing algorithm.
|
4
|
+
* Added merging of configs for multiple Hirb.enable calls.
|
5
|
+
* Added :max_fields, :hide_empty, :delete_callbacks, :resize, :header_filter
|
6
|
+
and :return_rows options to Table.
|
7
|
+
* Added escaping for \t and \r in Table.
|
8
|
+
* Renamed Table's :no_newlines option to :escape_special_chars.
|
9
|
+
* Removed Table's :field_lengths option.
|
10
|
+
* Removed Menu's :validate_one option.
|
11
|
+
* Bug fix for table header of a basic array.
|
12
|
+
* Deprecating Hirb.config_file + View.enable in next release.
|
13
|
+
|
1
14
|
== 0.2.9
|
2
15
|
* Added newline filtering and :no_newlines option for table helper.
|
3
16
|
* Added default filters for hashes that have hash values.
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -151,7 +151,7 @@ app's needs}[http://github.com/cldwalker/tag-tree].
|
|
151
151
|
|
152
152
|
== Credits
|
153
153
|
* Chrononaut for vertical table helper.
|
154
|
-
* crafterm, spastorino and joshua for bug fixes.
|
154
|
+
* crafterm, spastorino, xaviershay and joshua for bug fixes.
|
155
155
|
|
156
156
|
== Bugs/Issues
|
157
157
|
Please report them {on github}[http://github.com/cldwalker/hirb/issues].
|
data/VERSION.yml
CHANGED
data/lib/hirb.rb
CHANGED
@@ -17,8 +17,8 @@ require 'hirb/menu'
|
|
17
17
|
|
18
18
|
# Most of Hirb's functionality currently resides in Hirb::View.
|
19
19
|
# For an in-depth tutorial on creating and configuring views see Hirb::Formatter.
|
20
|
-
# Hirb
|
21
|
-
#
|
20
|
+
# Hirb can have multiple config files defined by config_files(). These config files
|
21
|
+
# have the following top level keys:
|
22
22
|
# [:output] This hash is used by the formatter object. See Hirb::Formatter.config for its format.
|
23
23
|
# [:width] Width of the terminal/console. Defaults to DEFAULT_WIDTH or possibly autodetected when Hirb is enabled.
|
24
24
|
# [:height] Height of the terminal/console. Defaults to DEFAULT_HEIGHT or possibly autodetected when Hirb is enabled.
|
@@ -30,6 +30,8 @@ require 'hirb/menu'
|
|
30
30
|
|
31
31
|
module Hirb
|
32
32
|
class <<self
|
33
|
+
attr_accessor :config_files, :config
|
34
|
+
|
33
35
|
# Enables view functionality. See Hirb::View.enable for details.
|
34
36
|
def enable(options={}, &block)
|
35
37
|
View.enable(options, &block)
|
@@ -39,15 +41,21 @@ module Hirb
|
|
39
41
|
def disable
|
40
42
|
View.disable
|
41
43
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
|
45
|
+
# Array of config files which are merged sequentially to produce config.
|
46
|
+
# Defaults to config/hirb.yml and ~/.hirb_yml
|
47
|
+
def config_files
|
48
|
+
@config_files ||= default_config_files
|
46
49
|
end
|
47
50
|
|
48
51
|
#:stopdoc:
|
49
|
-
def config_file
|
50
|
-
|
52
|
+
def config_file
|
53
|
+
puts "Hirb.config_file is *deprecated*. Use Hirb.config_files"
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_config_files
|
57
|
+
[File.join(Util.find_home, ".hirb.yml")] +
|
58
|
+
(File.exists?('config/hirb.yml') ? ['config/hirb.yml'] : [])
|
51
59
|
end
|
52
60
|
|
53
61
|
def read_config_file(file=config_file)
|
@@ -55,7 +63,12 @@ module Hirb
|
|
55
63
|
end
|
56
64
|
|
57
65
|
def config(reload=false)
|
58
|
-
|
66
|
+
if (@config.nil? || reload)
|
67
|
+
@config = config_files.inject({}) {|acc,e|
|
68
|
+
Util.recursive_hash_merge(acc,read_config_file(e))
|
69
|
+
}
|
70
|
+
end
|
71
|
+
@config
|
59
72
|
end
|
60
73
|
#:startdoc:
|
61
74
|
end
|
data/lib/hirb/formatter.rb
CHANGED
@@ -158,11 +158,7 @@ module Hirb
|
|
158
158
|
end
|
159
159
|
|
160
160
|
def determine_output_class(output)
|
161
|
-
|
162
|
-
output[0].class
|
163
|
-
else
|
164
|
-
output.class
|
165
|
-
end
|
161
|
+
output.is_a?(Array) ? output[0].class : output.class
|
166
162
|
end
|
167
163
|
|
168
164
|
def call_output_method(output_method, output)
|
@@ -5,7 +5,7 @@ class Hirb::Helpers::ActiveRecordTable < Hirb::Helpers::ObjectTable
|
|
5
5
|
# Options:
|
6
6
|
# :fields- Can be any attribute, column or not. If not given, this defaults to the database table's columns.
|
7
7
|
def self.render(rows, options={})
|
8
|
-
rows =
|
8
|
+
rows = Array(rows)
|
9
9
|
options[:fields] ||=
|
10
10
|
begin
|
11
11
|
fields = rows.first.class.column_names
|
@@ -5,9 +5,8 @@ class Hirb::Helpers::ObjectTable < Hirb::Helpers::Table
|
|
5
5
|
# :fields- Methods of the object to represent as columns. Defaults to [:to_s].
|
6
6
|
def self.render(rows, options ={})
|
7
7
|
options[:fields] ||= [:to_s]
|
8
|
-
options[:headers]
|
9
|
-
|
10
|
-
item_hashes = options[:fields].empty? ? [] : rows.inject([]) {|t,item|
|
8
|
+
options[:headers] ||= {:to_s=>'value'} if options[:fields] == [:to_s]
|
9
|
+
item_hashes = options[:fields].empty? ? [] : Array(rows).inject([]) {|t,item|
|
11
10
|
t << options[:fields].inject({}) {|h,f| h[f] = item.send(f); h}
|
12
11
|
}
|
13
12
|
super(item_hashes, options)
|
data/lib/hirb/helpers/table.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'hirb/helpers/table/filters'
|
2
|
+
require 'hirb/helpers/table/resizer'
|
3
|
+
|
1
4
|
module Hirb
|
2
5
|
# Base Table class from which other table classes inherit.
|
3
6
|
# By default, a table is constrained to a default width but this can be adjusted
|
@@ -56,61 +59,78 @@ module Hirb
|
|
56
59
|
# derived from http://gist.github.com/72234
|
57
60
|
class Helpers::Table
|
58
61
|
BORDER_LENGTH = 3 # " | " and "-+-" are the borders
|
62
|
+
MIN_FIELD_LENGTH = 3
|
59
63
|
class TooManyFieldsForWidthError < StandardError; end
|
60
64
|
|
61
65
|
class << self
|
62
66
|
|
63
67
|
# Main method which returns a formatted table.
|
64
68
|
# ==== Options:
|
65
|
-
# [
|
66
|
-
# [
|
67
|
-
#
|
68
|
-
# [
|
69
|
-
#
|
70
|
-
#
|
71
|
-
# [
|
72
|
-
#
|
73
|
-
# [
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# [
|
77
|
-
#
|
78
|
-
# [
|
79
|
-
#
|
80
|
-
# [
|
81
|
-
# [
|
82
|
-
#
|
69
|
+
# [*:fields*] An array which overrides the default fields and can be used to indicate field order.
|
70
|
+
# [*:headers*] A hash of fields and their header names. Fields that aren't specified here default to their name.
|
71
|
+
# This option can also be an array but only for array rows.
|
72
|
+
# [*:max_fields*] A hash of fields and their maximum allowed lengths. Maximum length can also be a percentage of the total width
|
73
|
+
# (decimal less than one). When a field exceeds it's maximum then it's
|
74
|
+
# truncated and has a ... appended to it. Fields that aren't specified have no maximum.
|
75
|
+
# [*:max_width*] The maximum allowed width of all fields put together including field borders. Only valid when :resize is true.
|
76
|
+
# Default is Hirb::View.width.
|
77
|
+
# [*:resize*] Resizes table to display all columns in allowed :max_width. Default is true. Setting this false will display the full
|
78
|
+
# length of each field.
|
79
|
+
# [*:number*] When set to true, numbers rows by adding a :hirb_number column as the first column. Default is false.
|
80
|
+
# [*:change_fields*] A hash to change old field names to new field names. This can also be an array of new names but only for array rows.
|
81
|
+
# This is useful when wanting to change auto-generated keys to more user-friendly names i.e. for array rows.
|
82
|
+
# [*:filters*] A hash of fields and their filters, applied to every row in a field. A filter can be a proc, an instance method
|
83
|
+
# applied to the field value or a Filters method. Also see the filter_classes attribute below.
|
84
|
+
# [*:header_filter*] A filter, like one in :filters, that is applied to all headers after the :headers option.
|
85
|
+
# [*:filter_any*] When set to true, any cell defaults to being filtered by its class in :filter_classes.
|
86
|
+
# Default Hirb::Helpers::Table.filter_any().
|
87
|
+
# [*:filter_classes*] Hash which maps classes to filters. Default is Hirb::Helpers::Table.filter_classes().
|
88
|
+
# [*:vertical*] When set to true, renders a vertical table using Hirb::Helpers::VerticalTable. Default is false.
|
89
|
+
# [*:all_fields*] When set to true, renders fields in all rows. Valid only in rows that are hashes. Default is false.
|
90
|
+
# [*:description*] When set to true, renders row count description at bottom. Default is true.
|
91
|
+
# [*:escape_special_chars*] When set to true, escapes special characters \n,\t,\r so they don't disrupt tables. Default is false for
|
92
|
+
# vertical tables and true for anything else.
|
93
|
+
# [*:return_rows*] When set to true, returns rows that have been initialized but not rendered. Default is false.
|
83
94
|
# Examples:
|
84
95
|
# Hirb::Helpers::Table.render [[1,2], [2,3]]
|
85
|
-
# Hirb::Helpers::Table.render [[1,2], [2,3]], :
|
86
|
-
# Hirb::Helpers::Table.render [['a',1], ['b',2]], :change_fields=>%w{letters numbers}
|
96
|
+
# Hirb::Helpers::Table.render [[1,2], [2,3]], :max_fields=>{0=>10}, :header_filter=>:capitalize
|
97
|
+
# Hirb::Helpers::Table.render [['a',1], ['b',2]], :change_fields=>%w{letters numbers}, :max_fields=>{'numbers'=>0.4}
|
87
98
|
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}]
|
88
99
|
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :headers=>{:weight=>"Weight(lbs)"}
|
89
100
|
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :filters=>{:age=>[:to_f]}
|
90
101
|
def render(rows, options={})
|
91
|
-
options[:vertical] ? Helpers::VerticalTable.render(rows, options) :
|
102
|
+
options[:vertical] ? Helpers::VerticalTable.render(rows, options) :
|
103
|
+
options[:return_rows] ? new(rows, options).instance_variable_get("@rows") : new(rows, options).render
|
92
104
|
rescue TooManyFieldsForWidthError
|
93
105
|
$stderr.puts "", "** Error: Too many fields for the current width. Configure your width " +
|
94
106
|
"and/or fields to avoid this error. Defaulting to a vertical table. **"
|
95
107
|
Helpers::VerticalTable.render(rows, options)
|
96
108
|
end
|
109
|
+
|
110
|
+
# A hash which maps a cell value's class to a filter. This serves to set a default filter per field if all of its
|
111
|
+
# values are a class in this hash. By default, Array values are comma joined and Hashes are inspected.
|
112
|
+
# See the :filter_any option to apply this filter per value.
|
113
|
+
attr_accessor :filter_classes
|
114
|
+
# Boolean which sets the default for :filter_any option.
|
115
|
+
attr_accessor :filter_any
|
116
|
+
# Holds last table object created
|
117
|
+
attr_accessor :last_table
|
97
118
|
end
|
98
|
-
|
119
|
+
self.filter_classes = { Array=>:comma_join, Hash=>:inspect }
|
120
|
+
|
99
121
|
#:stopdoc:
|
122
|
+
attr_accessor :width, :max_fields, :field_lengths, :fields
|
100
123
|
def initialize(rows, options={})
|
101
|
-
@options = {:description=>true, :filters=>{}, :change_fields=>{}, :
|
102
|
-
|
124
|
+
@options = {:description=>true, :filters=>{}, :change_fields=>{}, :escape_special_chars=>true,
|
125
|
+
:filter_any=>Helpers::Table.filter_any, :resize=>true}.merge(options)
|
103
126
|
@fields = set_fields(rows)
|
104
|
-
@rows =
|
105
|
-
@headers =
|
106
|
-
if @options.has_key?(:headers)
|
107
|
-
@headers = @options[:headers].is_a?(Hash) ? @headers.merge(@options[:headers]) :
|
108
|
-
(@options[:headers].is_a?(Array) ? array_to_indices_hash(@options[:headers]) : @options[:headers])
|
109
|
-
end
|
127
|
+
@rows = set_rows(rows)
|
128
|
+
@headers = set_headers
|
110
129
|
if @options[:number]
|
111
130
|
@headers[:hirb_number] = "number"
|
112
131
|
@fields.unshift :hirb_number
|
113
132
|
end
|
133
|
+
Helpers::Table.last_table = self
|
114
134
|
end
|
115
135
|
|
116
136
|
def set_fields(rows)
|
@@ -124,13 +144,14 @@ module Hirb
|
|
124
144
|
rows[0].is_a?(Array) ? (0..rows[0].length - 1).to_a : []
|
125
145
|
end
|
126
146
|
end
|
147
|
+
@options[:change_fields] = array_to_indices_hash(@options[:change_fields]) if @options[:change_fields].is_a?(Array)
|
127
148
|
@options[:change_fields].each do |oldf, newf|
|
128
149
|
(index = fields.index(oldf)) ? fields[index] = newf : fields << newf
|
129
150
|
end
|
130
151
|
fields
|
131
152
|
end
|
132
153
|
|
133
|
-
def
|
154
|
+
def set_rows(rows)
|
134
155
|
rows = Array(rows)
|
135
156
|
if rows[0].is_a?(Array)
|
136
157
|
rows = rows.inject([]) {|new_rows, row|
|
@@ -142,13 +163,28 @@ module Hirb
|
|
142
163
|
end
|
143
164
|
rows = filter_values(rows)
|
144
165
|
rows.each_with_index {|e,i| e[:hirb_number] = (i + 1).to_s} if @options[:number]
|
145
|
-
|
166
|
+
deleted_callbacks = Array(@options[:delete_callbacks]).map {|e| "#{e}_callback" }
|
167
|
+
(methods.grep(/_callback$/) - deleted_callbacks).sort.each do |meth|
|
146
168
|
rows = send(meth, rows, @options.dup)
|
147
169
|
end
|
148
170
|
validate_values(rows)
|
149
171
|
rows
|
150
172
|
end
|
151
|
-
|
173
|
+
|
174
|
+
def set_headers
|
175
|
+
headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
|
176
|
+
if @options.has_key?(:headers)
|
177
|
+
headers = @options[:headers].is_a?(Hash) ? headers.merge(@options[:headers]) :
|
178
|
+
(@options[:headers].is_a?(Array) ? array_to_indices_hash(@options[:headers]) : @options[:headers])
|
179
|
+
end
|
180
|
+
if @options[:header_filter]
|
181
|
+
headers.each {|k,v|
|
182
|
+
headers[k] = call_filter(@options[:header_filter], v)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
headers
|
186
|
+
end
|
187
|
+
|
152
188
|
def render
|
153
189
|
body = []
|
154
190
|
unless @rows.length == 0
|
@@ -203,57 +239,30 @@ module Hirb
|
|
203
239
|
|
204
240
|
def setup_field_lengths
|
205
241
|
@field_lengths = default_field_lengths
|
206
|
-
if @options[:
|
207
|
-
@
|
242
|
+
if @options[:resize]
|
243
|
+
raise TooManyFieldsForWidthError if @fields.size > self.actual_width.to_f / MIN_FIELD_LENGTH
|
244
|
+
Resizer.resize!(self)
|
208
245
|
else
|
209
|
-
|
210
|
-
restrict_field_lengths(@field_lengths, table_max_width) if table_max_width
|
246
|
+
enforce_field_constraints
|
211
247
|
end
|
212
248
|
end
|
213
|
-
|
214
|
-
def
|
215
|
-
|
216
|
-
original_field_lengths = field_lengths.dup
|
217
|
-
@min_field_length = BORDER_LENGTH
|
218
|
-
adjust_long_fields(field_lengths, max_width)
|
219
|
-
rescue TooManyFieldsForWidthError
|
220
|
-
raise
|
221
|
-
rescue
|
222
|
-
default_restrict_field_lengths(field_lengths, original_field_lengths, max_width)
|
249
|
+
|
250
|
+
def enforce_field_constraints
|
251
|
+
max_fields.each {|k,max| @field_lengths[k] = max if @field_lengths[k].to_i > max }
|
223
252
|
end
|
224
253
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
while total_length > max_width
|
230
|
-
raise TooManyFieldsForWidthError if @fields.size > max_width.to_f / @min_field_length
|
231
|
-
average_field_length = total_length / @fields.size.to_f
|
232
|
-
long_lengths = field_lengths.values.select {|e| e > average_field_length}
|
233
|
-
if long_lengths.empty?
|
234
|
-
raise "Algorithm didn't work, resort to default"
|
235
|
-
else
|
236
|
-
total_long_field_length = (long_lengths.inject {|t,n| t += n}) * max_width/total_length
|
237
|
-
average_long_field_length = total_long_field_length / long_lengths.size
|
238
|
-
field_lengths.each {|f,length|
|
239
|
-
field_lengths[f] = average_long_field_length if length > average_long_field_length
|
240
|
-
}
|
241
|
-
end
|
242
|
-
total_length = field_lengths.values.inject {|t,n| t += n}
|
243
|
-
end
|
254
|
+
def max_fields
|
255
|
+
@max_fields ||= (@options[:max_fields] ||= {}).each {|k,v|
|
256
|
+
@options[:max_fields][k] = (actual_width * v.to_f.abs).floor if v.to_f.abs < 1
|
257
|
+
}
|
244
258
|
end
|
245
259
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
original_field_lengths.each {|k,v| field_lengths[k] = (v / original_total_length.to_f * max_width).to_i }
|
253
|
-
else
|
254
|
-
# set all fields the same if nothing else works
|
255
|
-
field_lengths.each {|k,v| field_lengths[k] = max_width / @fields.size}
|
256
|
-
end
|
260
|
+
def actual_width
|
261
|
+
@actual_width ||= self.width - (@fields.size * BORDER_LENGTH + 1)
|
262
|
+
end
|
263
|
+
|
264
|
+
def width
|
265
|
+
@width ||= @options[:max_width] || View.width
|
257
266
|
end
|
258
267
|
|
259
268
|
# find max length for each field; start with the headers
|
@@ -269,32 +278,37 @@ module Hirb
|
|
269
278
|
end
|
270
279
|
|
271
280
|
def set_filter_defaults(rows)
|
272
|
-
|
273
|
-
|
281
|
+
@filter_classes.each do |klass, filter|
|
282
|
+
@fields.each {|field|
|
283
|
+
if rows.all? {|r| r[field].class == klass }
|
284
|
+
@options[:filters][field] ||= filter
|
285
|
+
end
|
286
|
+
}
|
274
287
|
end
|
275
288
|
end
|
276
289
|
|
277
290
|
def filter_values(rows)
|
278
|
-
|
291
|
+
@filter_classes = Helpers::Table.filter_classes.merge @options[:filter_classes] || {}
|
292
|
+
set_filter_defaults(rows) unless @options[:filter_any]
|
279
293
|
rows.map {|row|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
row[f].send(*@options[:filters][f])
|
285
|
-
else
|
286
|
-
new_row[f] = row[f]
|
287
|
-
end
|
294
|
+
@fields.inject({}) {|new_row,f|
|
295
|
+
(filter = @options[:filters][f]) || (@options[:filter_any] && (filter = @filter_classes[row[f].class]))
|
296
|
+
new_row[f] = filter ? call_filter(filter, row[f]) : row[f]
|
297
|
+
new_row
|
288
298
|
}
|
289
|
-
new_row
|
290
299
|
}
|
291
300
|
end
|
292
301
|
|
302
|
+
def call_filter(filter, val)
|
303
|
+
filter.is_a?(Proc) ? filter.call(val) :
|
304
|
+
val.respond_to?(Array(filter)[0]) ? val.send(*filter) : Filters.send(filter, val)
|
305
|
+
end
|
306
|
+
|
293
307
|
def validate_values(rows)
|
294
308
|
rows.each {|row|
|
295
309
|
@fields.each {|f|
|
296
310
|
row[f] = row[f].to_s || ''
|
297
|
-
row[f].gsub
|
311
|
+
row[f] = row[f].gsub(/(\t|\r|\n)/) {|e| e.dump.gsub('"','') } if @options[:escape_special_chars]
|
298
312
|
}
|
299
313
|
}
|
300
314
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Hirb::Helpers::Table
|
2
|
+
# Contains filter methods used by :filters option. To define a custom filter, simply open this module and create a method
|
3
|
+
# that take one argument, the value you will be filtering.
|
4
|
+
module Filters
|
5
|
+
extend self
|
6
|
+
def comma_join(arr) #:nodoc:
|
7
|
+
arr.join(', ')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Hirb::Helpers::Table
|
2
|
+
# Resizes a table's fields to the table's max width.
|
3
|
+
class Resizer
|
4
|
+
# Modifies field_lengths to fit within width. Also enforces a table's max_fields.
|
5
|
+
def self.resize!(table)
|
6
|
+
obj = new(table)
|
7
|
+
obj.resize
|
8
|
+
obj.field_lengths
|
9
|
+
end
|
10
|
+
|
11
|
+
#:stopdoc:
|
12
|
+
attr_reader :field_lengths
|
13
|
+
def initialize(table)
|
14
|
+
@table, @width, @field_size = table, table.actual_width, table.fields.size
|
15
|
+
@field_lengths = table.field_lengths
|
16
|
+
@original_field_lengths = @field_lengths.dup
|
17
|
+
end
|
18
|
+
|
19
|
+
def resize
|
20
|
+
adjust_long_fields || default_restrict_field_lengths
|
21
|
+
@table.enforce_field_constraints
|
22
|
+
add_extra_width
|
23
|
+
end
|
24
|
+
|
25
|
+
# Simple algorithm which allows smaller fields to be displayed while
|
26
|
+
# restricting longer fields to an average_long_field
|
27
|
+
def adjust_long_fields
|
28
|
+
while (total_length = sum(@field_lengths.values)) > @width
|
29
|
+
average_field = total_length / @field_size.to_f
|
30
|
+
long_lengths = @field_lengths.values.select {|e| e > average_field }
|
31
|
+
return false if long_lengths.empty?
|
32
|
+
|
33
|
+
# adjusts average long field by ratio with @width
|
34
|
+
average_long_field = sum(long_lengths)/long_lengths.size * @width/total_length
|
35
|
+
@field_lengths.each {|f,length|
|
36
|
+
@field_lengths[f] = average_long_field if length > average_long_field
|
37
|
+
}
|
38
|
+
end
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Produces a field_lengths which meets the @width requirement
|
43
|
+
def default_restrict_field_lengths
|
44
|
+
original_total_length = sum @original_field_lengths.values
|
45
|
+
# set fields by their relative weight to original length
|
46
|
+
new_lengths = @original_field_lengths.inject({}) {|t,(k,v)|
|
47
|
+
t[k] = (v / original_total_length.to_f * @width).to_i; t }
|
48
|
+
|
49
|
+
# set all fields the same if relative doesn't work
|
50
|
+
unless new_lengths.values.all? {|e| e > MIN_FIELD_LENGTH} && (sum(new_lengths.values) <= @width)
|
51
|
+
new_lengths = @field_lengths.inject({}) {|t,(k,v)| t[k] = @width / @field_size; t }
|
52
|
+
end
|
53
|
+
@field_lengths.each {|k,v| @field_lengths[k] = new_lengths[k] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_extra_width
|
57
|
+
added_width = 0
|
58
|
+
extra_width = @width - sum(@field_lengths.values)
|
59
|
+
unmaxed_fields = @field_lengths.keys.select {|f| !remaining_width(f).zero? }
|
60
|
+
# order can affect which one gets the remainder so let's keep it consistent
|
61
|
+
unmaxed_fields = unmaxed_fields.sort_by {|e| e.to_s}
|
62
|
+
|
63
|
+
unmaxed_fields.each_with_index do |f, i|
|
64
|
+
extra_per_field = (extra_width - added_width) / (unmaxed_fields.size - i)
|
65
|
+
add_to_field = remaining_width(f) < extra_per_field ? remaining_width(f) : extra_per_field
|
66
|
+
added_width += add_to_field
|
67
|
+
@field_lengths[f] += add_to_field
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def remaining_width(field)
|
72
|
+
(@remaining_width ||= {})[field] ||= begin
|
73
|
+
(@table.max_fields[field] || @original_field_lengths[field]) - @field_lengths[field]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def sum(arr)
|
78
|
+
arr.inject {|t,e| t += e }
|
79
|
+
end
|
80
|
+
#:startdoc:
|
81
|
+
end
|
82
|
+
end
|