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 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
@@ -1,6 +1,6 @@
1
1
  The MIT LICENSE
2
2
 
3
- Copyright (c) 2009 Gabriel Horner
3
+ Copyright (c) 2010 Gabriel Horner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
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
@@ -1,4 +1,5 @@
1
1
  ---
2
- :major: 0
3
2
  :minor: 2
4
- :patch: 9
3
+ :patch: 10
4
+ :build:
5
+ :major: 0
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 has an optional yaml config file defined by config_file(). This config file
21
- # has the following top level keys:
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
- # Default is config/hirb.yml or ~/hirb.yml in that order.
43
- def config_file
44
- @config_file ||= File.exists?('config/hirb.yml') ? 'config/hirb.yml' :
45
- File.expand_path(File.join(ENV["HOME"] || ".", ".hirb.yml"))
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=(value)
50
- @config_file = value
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
- @config = (@config.nil? || reload) ? read_config_file : @config
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
@@ -158,11 +158,7 @@ module Hirb
158
158
  end
159
159
 
160
160
  def determine_output_class(output)
161
- if output.is_a?(Array)
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 = [rows] unless rows.is_a?(Array)
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] = {:to_s=>'value'} if options[:fields] == [:to_s]
9
- rows = [rows] unless rows.is_a?(Array)
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)
@@ -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
- # [:fields] An array which overrides the default fields and can be used to indicate field order.
66
- # [:headers] A hash of fields and their header names. Fields that aren't specified here default to their name.
67
- # This option can also be an array but only for array rows.
68
- # [:field_lengths] A hash of fields and their maximum allowed lengths. If a field exceeds it's maximum
69
- # length than it's truncated and has a ... appended to it. Fields that aren't specified here have no maximum allowed
70
- # length.
71
- # [:max_width] The maximum allowed width of all fields put together. This option is enforced except when the field_lengths option is set.
72
- # This doesn't count field borders as part of the total.
73
- # [:number] When set to true, numbers rows by adding a :hirb_number column as the first column. Default is false.
74
- # [: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.
75
- # This is useful when wanting to change auto-generated keys to more user-friendly names i.e. for array rows.
76
- # [:filters] A hash of fields and the filters that each row in the field must run through. The filter converts the cell's value by applying
77
- # a given proc or an array containing a method and optional arguments to it.
78
- # [:vertical] When set to true, renders a vertical table using Hirb::Helpers::VerticalTable. Default is false.
79
- # [:all_fields] When set to true, renders fields in all rows. Valid only in rows that are hashes. Default is false.
80
- # [:description] When set to true, renders row count description at bottom. Default is true.
81
- # [:no_newlines] When set to true, stringifies newlines so they don't disrupt tables. Default is false for vertical tables
82
- # and true for anything else.
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]], :field_lengths=>{0=>10}
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) : new(rows, options).render
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=>{}, :no_newlines=>true}.merge(options)
102
- @options[:change_fields] = array_to_indices_hash(@options[:change_fields]) if @options[:change_fields].is_a?(Array)
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 = setup_rows(rows)
105
- @headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
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 setup_rows(rows)
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
- methods.grep(/_callback$/).sort.each do |meth|
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[:field_lengths]
207
- @field_lengths.merge!(@options[:field_lengths])
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
- table_max_width = @options.has_key?(:max_width) ? @options[:max_width] : View.width
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 restrict_field_lengths(field_lengths, max_width)
215
- max_width -= @fields.size * BORDER_LENGTH + 1
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
- # Simple algorithm which given a max width, allows smaller fields to be displayed while
226
- # restricting longer fields at an average_long_field_length.
227
- def adjust_long_fields(field_lengths, max_width)
228
- total_length = field_lengths.values.inject {|t,n| t += n}
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
- # Produces a field_lengths which meets the max_width requirement
247
- def default_restrict_field_lengths(field_lengths, original_field_lengths, max_width)
248
- original_total_length = original_field_lengths.values.inject {|t,n| t += n}
249
- relative_lengths = original_field_lengths.values.map {|v| (v / original_total_length.to_f * max_width).to_i }
250
- # set fields by their relative weight to original length
251
- if relative_lengths.all? {|e| e > @min_field_length} && (relative_lengths.inject {|a,e| a += e} <= max_width)
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
- if @options[:_original_class] == Hash && @fields[1] && rows.any? {|e| e[@fields[1]].is_a?(Hash) }
273
- (@options[:filters] ||= {})[@fields[1]] ||= :inspect
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
- set_filter_defaults(rows)
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
- new_row = {}
281
- @fields.each {|f|
282
- if @options[:filters][f]
283
- new_row[f] = @options[:filters][f].is_a?(Proc) ? @options[:filters][f].call(row[f]) :
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!("\n", '\n') if @options[:no_newlines]
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