hirb 0.2.9 → 0.2.10

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