red-arrow 4.0.1 → 7.0.0

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -0
  3. data/README.md +23 -0
  4. data/ext/arrow/arrow.cpp +3 -0
  5. data/ext/arrow/converters.cpp +5 -0
  6. data/ext/arrow/converters.hpp +126 -0
  7. data/ext/arrow/extconf.rb +13 -0
  8. data/ext/arrow/memory-view.cpp +311 -0
  9. data/ext/arrow/memory-view.hpp +26 -0
  10. data/ext/arrow/raw-records.cpp +1 -0
  11. data/ext/arrow/values.cpp +1 -0
  12. data/lib/arrow/aggregate-node-options.rb +35 -0
  13. data/lib/arrow/aggregation.rb +46 -0
  14. data/lib/arrow/array-builder.rb +5 -0
  15. data/lib/arrow/array.rb +12 -0
  16. data/lib/arrow/binary-dictionary-array-builder.rb +27 -0
  17. data/lib/arrow/buffer.rb +10 -6
  18. data/lib/arrow/column-containable.rb +100 -1
  19. data/lib/arrow/constructor-arguments-gc-guardable.rb +25 -0
  20. data/lib/arrow/datum.rb +102 -0
  21. data/lib/arrow/equal-options.rb +38 -0
  22. data/lib/arrow/expression.rb +48 -0
  23. data/lib/arrow/file-system.rb +34 -0
  24. data/lib/arrow/function.rb +52 -0
  25. data/lib/arrow/group.rb +116 -124
  26. data/lib/arrow/loader.rb +58 -0
  27. data/lib/arrow/map-array-builder.rb +109 -0
  28. data/lib/arrow/map-array.rb +26 -0
  29. data/lib/arrow/map-data-type.rb +89 -0
  30. data/lib/arrow/path-extension.rb +1 -1
  31. data/lib/arrow/record-batch-reader.rb +41 -0
  32. data/lib/arrow/record-batch.rb +0 -2
  33. data/lib/arrow/s3-global-options.rb +38 -0
  34. data/lib/arrow/scalar.rb +32 -0
  35. data/lib/arrow/slicer.rb +44 -143
  36. data/lib/arrow/sort-key.rb +61 -55
  37. data/lib/arrow/sort-options.rb +8 -8
  38. data/lib/arrow/source-node-options.rb +32 -0
  39. data/lib/arrow/string-dictionary-array-builder.rb +27 -0
  40. data/lib/arrow/symbol-values-appendable.rb +34 -0
  41. data/lib/arrow/table-concatenate-options.rb +36 -0
  42. data/lib/arrow/table-formatter.rb +141 -17
  43. data/lib/arrow/table-list-formatter.rb +5 -3
  44. data/lib/arrow/table-loader.rb +119 -44
  45. data/lib/arrow/table-saver.rb +36 -5
  46. data/lib/arrow/table-table-formatter.rb +7 -31
  47. data/lib/arrow/table.rb +112 -40
  48. data/lib/arrow/version.rb +1 -1
  49. data/red-arrow.gemspec +1 -9
  50. data/test/helper.rb +3 -0
  51. data/test/raw-records/test-dense-union-array.rb +14 -0
  52. data/test/raw-records/test-list-array.rb +19 -0
  53. data/test/raw-records/test-map-array.rb +441 -0
  54. data/test/raw-records/test-sparse-union-array.rb +14 -0
  55. data/test/raw-records/test-struct-array.rb +15 -0
  56. data/test/test-array-builder.rb +7 -0
  57. data/test/test-array.rb +34 -0
  58. data/test/test-binary-dictionary-array-builder.rb +103 -0
  59. data/test/test-boolean-scalar.rb +26 -0
  60. data/test/test-csv-loader.rb +8 -8
  61. data/test/test-expression.rb +40 -0
  62. data/test/test-float-scalar.rb +46 -0
  63. data/test/test-function.rb +210 -0
  64. data/test/test-group.rb +75 -51
  65. data/test/test-map-array-builder.rb +110 -0
  66. data/test/test-map-array.rb +33 -0
  67. data/test/test-map-data-type.rb +36 -0
  68. data/test/test-memory-view.rb +434 -0
  69. data/test/test-record-batch-reader.rb +46 -0
  70. data/test/test-record-batch.rb +42 -0
  71. data/test/test-slicer.rb +166 -167
  72. data/test/test-string-dictionary-array-builder.rb +103 -0
  73. data/test/test-table.rb +376 -56
  74. data/test/values/test-dense-union-array.rb +14 -0
  75. data/test/values/test-list-array.rb +17 -0
  76. data/test/values/test-map-array.rb +433 -0
  77. data/test/values/test-sparse-union-array.rb +14 -0
  78. data/test/values/test-struct-array.rb +15 -0
  79. metadata +117 -168
@@ -0,0 +1,36 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ module Arrow
19
+ class TableConcatenateOptions
20
+ class << self
21
+ # @api private
22
+ def try_convert(value)
23
+ case value
24
+ when Hash
25
+ options = new
26
+ value.each do |k, v|
27
+ options.public_send("#{k}=", value)
28
+ end
29
+ options
30
+ else
31
+ nil
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -18,6 +18,125 @@
18
18
  module Arrow
19
19
  # TODO: Almost codes should be implemented in Apache Arrow C++.
20
20
  class TableFormatter
21
+ # @private
22
+ class ColumnFormatter
23
+ attr_reader :column
24
+ attr_reader :head_values
25
+ attr_reader :tail_values
26
+ attr_reader :sample_values
27
+ def initialize(column, head_values, tail_values)
28
+ @column = column
29
+ @head_values = head_values
30
+ @tail_values = tail_values
31
+ @sample_values = head_values + tail_values
32
+ @field_value_widths = {}
33
+ end
34
+
35
+ def data_type
36
+ @data_type ||= @column.data_type
37
+ end
38
+
39
+ def name
40
+ @name ||= @column.name
41
+ end
42
+
43
+ def aligned_name
44
+ @aligned_name ||= format_aligned_name(name, data_type, @sample_values)
45
+ end
46
+
47
+ FLOAT_N_DIGITS = 10
48
+ FORMATTED_NULL = "(null)"
49
+
50
+ def format_value(value, width=0)
51
+ case value
52
+ when ::Time
53
+ value.iso8601
54
+ when Float
55
+ "%*f" % [[width, FLOAT_N_DIGITS].max, value]
56
+ when Integer
57
+ "%*d" % [width, value]
58
+ when Hash
59
+ formatted_values = data_type.fields.collect do |field|
60
+ field_name = field.name
61
+ field_value_width = compute_field_value_width(field, @sample_values)
62
+ formatted_name = format_value(field_name, 0)
63
+ formatted_value = format_value(value[field_name], field_value_width)
64
+ "#{formatted_name}: #{formatted_value}"
65
+ end
66
+ formatted = "{"
67
+ formatted << formatted_values.join(", ")
68
+ formatted << "}"
69
+ "%-*s" % [width, formatted]
70
+ when nil
71
+ "%*s" % [width, FORMATTED_NULL]
72
+ else
73
+ "%-*s" % [width, value.to_s]
74
+ end
75
+ end
76
+
77
+ private
78
+ def compute_field_value_width(field, sample_values)
79
+ unless @field_value_widths.key?(field)
80
+ field_name = field.name
81
+ field_sample_values = sample_values.collect do |v|
82
+ (v || {})[field_name]
83
+ end
84
+ field_aligned_name = format_aligned_name("",
85
+ field.data_type,
86
+ field_sample_values)
87
+ @field_value_widths[field] = field_aligned_name.size
88
+ end
89
+ @field_value_widths[field]
90
+ end
91
+
92
+ def format_aligned_name(name, data_type, sample_values)
93
+ case data_type
94
+ when TimestampDataType
95
+ "%*s" % [::Time.now.iso8601.size, name]
96
+ when IntegerDataType
97
+ have_null = false
98
+ have_negative = false
99
+ max_value = nil
100
+ sample_values.each do |value|
101
+ if value.nil?
102
+ have_null = true
103
+ else
104
+ if max_value.nil?
105
+ max_value = value.abs
106
+ else
107
+ max_value = [value.abs, max_value].max
108
+ end
109
+ have_negative = true if value.negative?
110
+ end
111
+ end
112
+ if max_value.nil?
113
+ width = 0
114
+ elsif max_value.zero?
115
+ width = 1
116
+ else
117
+ width = (Math.log10(max_value) + 1).truncate
118
+ end
119
+ width += 1 if have_negative # Need "-"
120
+ width = [width, FORMATTED_NULL.size].max if have_null
121
+ "%*s" % [width, name]
122
+ when FloatDataType, DoubleDataType
123
+ "%*s" % [FLOAT_N_DIGITS, name]
124
+ when StructDataType
125
+ field_widths = data_type.fields.collect do |field|
126
+ field_value_width = compute_field_value_width(field, sample_values)
127
+ field.name.size + ": ".size + field_value_width
128
+ end
129
+ width = "{}".size + field_widths.sum
130
+ if field_widths.size > 0
131
+ width += (", ".size * (field_widths.size - 1))
132
+ end
133
+ "%*s" % [width, name]
134
+ else
135
+ name
136
+ end
137
+ end
138
+ end
139
+
21
140
  def initialize(table, options={})
22
141
  @table = table
23
142
  @options = options
@@ -25,38 +144,43 @@ module Arrow
25
144
 
26
145
  def format
27
146
  text = ""
28
- columns = @table.columns
29
- format_header(text, columns)
30
-
31
147
  n_rows = @table.n_rows
32
- return text if n_rows.zero?
33
-
34
148
  border = @options[:border] || 10
35
- n_digits = (Math.log10(n_rows) + 1).truncate
149
+
36
150
  head_limit = [border, n_rows].min
37
- head_column_values = columns.collect do |column|
38
- column.each.take(head_limit)
151
+
152
+ tail_start = [border, n_rows - border].max
153
+ tail_limit = n_rows - tail_start
154
+
155
+ column_formatters = @table.columns.collect do |column|
156
+ head_values = column.each.take(head_limit)
157
+ if tail_limit > 0
158
+ tail_values = column.reverse_each.take(tail_limit).reverse
159
+ else
160
+ tail_values = []
161
+ end
162
+ ColumnFormatter.new(column, head_values, tail_values)
39
163
  end
164
+
165
+ format_header(text, column_formatters)
166
+ return text if n_rows.zero?
167
+
168
+ n_digits = (Math.log10(n_rows) + 1).truncate
40
169
  format_rows(text,
41
- columns,
42
- head_column_values.transpose,
170
+ column_formatters,
171
+ column_formatters.collect(&:head_values).transpose,
43
172
  n_digits,
44
173
  0)
45
174
  return text if n_rows <= border
46
175
 
47
- tail_start = [border, n_rows - border].max
48
- tail_limit = n_rows - tail_start
49
- tail_column_values = columns.collect do |column|
50
- column.reverse_each.take(tail_limit).reverse
51
- end
52
176
 
53
177
  if head_limit != tail_start
54
178
  format_ellipsis(text)
55
179
  end
56
180
 
57
181
  format_rows(text,
58
- columns,
59
- tail_column_values.transpose,
182
+ column_formatters,
183
+ column_formatters.collect(&:tail_values).transpose,
60
184
  n_digits,
61
185
  tail_start)
62
186
 
@@ -22,12 +22,14 @@ module Arrow
22
22
  def format_header(text, columns)
23
23
  end
24
24
 
25
- def format_rows(text, columns, rows, n_digits, start_offset)
25
+ def format_rows(text, column_formatters, rows, n_digits, start_offset)
26
26
  rows.each_with_index do |row, nth_row|
27
27
  text << ("=" * 20 + " #{start_offset + nth_row} " + "=" * 20 + "\n")
28
28
  row.each_with_index do |column_value, nth_column|
29
- column = columns[nth_column]
30
- text << "#{column.name}: #{column_value}\n"
29
+ column_formatter = column_formatters[nth_column]
30
+ formatted_name = column_formatter.name
31
+ formatted_value = column_formatter.format_value(column_value)
32
+ text << "#{formatted_name}: #{formatted_value}\n"
31
33
  end
32
34
  end
33
35
  end
@@ -15,6 +15,8 @@
15
15
  # specific language governing permissions and limitations
16
16
  # under the License.
17
17
 
18
+ require "open-uri"
19
+
18
20
  module Arrow
19
21
  class TableLoader
20
22
  class << self
@@ -31,6 +33,48 @@ module Arrow
31
33
  end
32
34
 
33
35
  def load
36
+ if @input.is_a?(URI)
37
+ custom_load_method_candidates = []
38
+ if @input.scheme
39
+ custom_load_method_candidates << "load_from_uri_#{@input.scheme}"
40
+ end
41
+ custom_load_method_candidates << "load_from_uri"
42
+ elsif @input.is_a?(String) and ::File.directory?(@input)
43
+ custom_load_method_candidates = ["load_from_directory"]
44
+ else
45
+ custom_load_method_candidates = ["load_from_file"]
46
+ end
47
+ custom_load_method_candidates.each do |custom_load_method|
48
+ next unless respond_to?(custom_load_method, true)
49
+ return __send__(custom_load_method)
50
+ end
51
+ available_schemes = []
52
+ (methods(true) | private_methods(true)).each do |name|
53
+ match_data = /\Aload_from_/.match(name.to_s)
54
+ if match_data
55
+ available_schemes << match_data.post_match
56
+ end
57
+ end
58
+ message = "Arrow::Table load source must be one of ["
59
+ message << available_schemes.join(", ")
60
+ message << "]: #{@input.inspect}"
61
+ raise ArgumentError, message
62
+ end
63
+
64
+ private
65
+ def load_from_uri_http
66
+ load_by_reader
67
+ end
68
+
69
+ def load_from_uri_https
70
+ load_by_reader
71
+ end
72
+
73
+ def load_from_file
74
+ load_by_reader
75
+ end
76
+
77
+ def load_by_reader
34
78
  format = @options[:format]
35
79
  custom_load_method = "load_as_#{format}"
36
80
  unless respond_to?(custom_load_method, true)
@@ -56,21 +100,24 @@ module Arrow
56
100
  end
57
101
  end
58
102
 
59
- private
60
103
  def fill_options
61
104
  if @options[:format] and @options.key?(:compression)
62
105
  return
63
106
  end
64
107
 
65
- if @input.is_a?(Buffer)
108
+ case @input
109
+ when Buffer
66
110
  info = {}
111
+ when URI
112
+ extension = PathExtension.new(@input.path)
113
+ info = extension.extract
67
114
  else
68
115
  extension = PathExtension.new(@input)
69
116
  info = extension.extract
70
117
  end
71
118
  format = info[:format]
72
119
  @options = @options.dup
73
- if format and respond_to?("load_as_#{format}", true)
120
+ if format
74
121
  @options[:format] ||= format.to_sym
75
122
  else
76
123
  @options[:format] ||= :arrow
@@ -81,10 +128,29 @@ module Arrow
81
128
  end
82
129
 
83
130
  def open_input_stream
84
- if @input.is_a?(Buffer)
85
- BufferInputStream.new(@input)
131
+ case @input
132
+ when Buffer
133
+ yield(BufferInputStream.new(@input))
134
+ when URI
135
+ @input.open do |ruby_input|
136
+ case @options[:format]
137
+ when :stream, :arrow_streaming
138
+ Gio::RubyInputStream.open(ruby_input) do |gio_input|
139
+ GIOInputStream.open(gio_input) do |input|
140
+ yield(input)
141
+ end
142
+ end
143
+ else
144
+ # TODO: We need to consider Ruby's GVL carefully to use
145
+ # Ruby object directly for input with other formats. We
146
+ # read data and use it as Buffer for now.
147
+ data = GLib::Bytes.new(ruby_input.read.freeze)
148
+ buffer = Buffer.new(data)
149
+ yield(BufferInputStream.new(buffer))
150
+ end
151
+ end
86
152
  else
87
- MemoryMappedInputStream.new(@input)
153
+ yield(MemoryMappedInputStream.new(@input))
88
154
  end
89
155
  end
90
156
 
@@ -100,32 +166,19 @@ module Arrow
100
166
  end
101
167
 
102
168
  def load_as_arrow
103
- input = nil
104
- reader = nil
105
- error = nil
106
- reader_class_candidates = [
107
- RecordBatchFileReader,
108
- RecordBatchStreamReader,
109
- ]
110
- reader_class_candidates.each do |reader_class_candidate|
111
- input = open_input_stream
112
- begin
113
- reader = reader_class_candidate.new(input)
114
- rescue Arrow::Error
115
- error = $!
116
- else
117
- break
118
- end
169
+ begin
170
+ load_as_arrow_file
171
+ rescue
172
+ load_as_arrows
119
173
  end
120
- raise error if reader.nil?
121
- load_raw(input, reader)
122
174
  end
123
175
 
124
176
  # @since 1.0.0
125
177
  def load_as_arrow_file
126
- input = open_input_stream
127
- reader = RecordBatchFileReader.new(input)
128
- load_raw(input, reader)
178
+ open_input_stream do |input|
179
+ reader = RecordBatchFileReader.new(input)
180
+ load_raw(input, reader)
181
+ end
129
182
  end
130
183
 
131
184
  # @deprecated Use `format: :arrow_file` instead.
@@ -133,34 +186,46 @@ module Arrow
133
186
  load_as_arrow_file
134
187
  end
135
188
 
189
+ # @since 7.0.0
190
+ def load_as_arrows
191
+ open_input_stream do |input|
192
+ reader = RecordBatchStreamReader.new(input)
193
+ load_raw(input, reader)
194
+ end
195
+ end
196
+
136
197
  # @since 1.0.0
137
198
  def load_as_arrow_streaming
138
- input = open_input_stream
139
- reader = RecordBatchStreamReader.new(input)
140
- load_raw(input, reader)
199
+ load_as_arrows
141
200
  end
142
201
 
143
202
  # @deprecated Use `format: :arrow_streaming` instead.
144
203
  def load_as_stream
145
- load_as_arrow_streaming
204
+ load_as_arrows
146
205
  end
147
206
 
148
207
  if Arrow.const_defined?(:ORCFileReader)
149
208
  def load_as_orc
150
- input = open_input_stream
151
- reader = ORCFileReader.new(input)
152
- field_indexes = @options[:field_indexes]
153
- reader.set_field_indexes(field_indexes) if field_indexes
154
- table = reader.read_stripes
155
- table.instance_variable_set(:@input, input)
156
- table
209
+ open_input_stream do |input|
210
+ reader = ORCFileReader.new(input)
211
+ field_indexes = @options[:field_indexes]
212
+ reader.set_field_indexes(field_indexes) if field_indexes
213
+ table = reader.read_stripes
214
+ table.instance_variable_set(:@input, input)
215
+ table
216
+ end
157
217
  end
158
218
  end
159
219
 
160
220
  def csv_load(options)
161
221
  options.delete(:format)
162
- if @input.is_a?(Buffer)
222
+ case @input
223
+ when Buffer
163
224
  CSVLoader.load(@input.data.to_s, **options)
225
+ when URI
226
+ @input.open do |input|
227
+ CSVLoader.load(input.read, **options)
228
+ end
164
229
  else
165
230
  CSVLoader.load(Pathname.new(@input), **options)
166
231
  end
@@ -177,11 +242,21 @@ module Arrow
177
242
  end
178
243
 
179
244
  def load_as_feather
180
- input = open_input_stream
181
- reader = FeatherFileReader.new(input)
182
- table = reader.read
183
- table.instance_variable_set(:@input, input)
184
- table
245
+ open_input_stream do |input|
246
+ reader = FeatherFileReader.new(input)
247
+ table = reader.read
248
+ table.instance_variable_set(:@input, input)
249
+ table
250
+ end
251
+ end
252
+
253
+ def load_as_json
254
+ open_input_stream do |input|
255
+ reader = JSONReader.new(input)
256
+ table = reader.read
257
+ table.instance_variable_set(:@input, input)
258
+ table
259
+ end
185
260
  end
186
261
  end
187
262
  end
@@ -32,6 +32,29 @@ module Arrow
32
32
  end
33
33
 
34
34
  def save
35
+ if @output.is_a?(URI)
36
+ custom_save_method = "save_to_uri"
37
+ else
38
+ custom_save_method = "save_to_file"
39
+ end
40
+ unless respond_to?(custom_save_method, true)
41
+ available_schemes = []
42
+ (methods(true) | private_methods(true)).each do |name|
43
+ match_data = /\Asave_to_/.match(name.to_s)
44
+ if match_data
45
+ available_schemes << match_data.post_match
46
+ end
47
+ end
48
+ message = "Arrow::Table save source must be one of ["
49
+ message << available_schemes.join(", ")
50
+ message << "]: #{@output.scheme.inspect}"
51
+ raise ArgumentError, message
52
+ end
53
+ __send__(custom_save_method)
54
+ end
55
+
56
+ private
57
+ def save_to_file
35
58
  format = @options[:format]
36
59
  custom_save_method = "save_as_#{format}"
37
60
  unless respond_to?(custom_save_method, true)
@@ -57,21 +80,24 @@ module Arrow
57
80
  end
58
81
  end
59
82
 
60
- private
61
83
  def fill_options
62
84
  if @options[:format] and @options.key?(:compression)
63
85
  return
64
86
  end
65
87
 
66
- if @output.is_a?(Buffer)
88
+ case @output
89
+ when Buffer
67
90
  info = {}
91
+ when URI
92
+ extension = PathExtension.new(@output.path)
93
+ info = extension.extract
68
94
  else
69
95
  extension = PathExtension.new(@output)
70
96
  info = extension.extract
71
97
  end
72
98
  format = info[:format]
73
99
  @options = @options.dup
74
- if format and respond_to?("save_as_#{format}", true)
100
+ if format
75
101
  @options[:format] ||= format.to_sym
76
102
  else
77
103
  @options[:format] ||= :arrow
@@ -125,14 +151,19 @@ module Arrow
125
151
  save_as_arrow_file
126
152
  end
127
153
 
154
+ # @since 7.0.0
155
+ def save_as_arrows
156
+ save_raw(RecordBatchStreamWriter)
157
+ end
158
+
128
159
  # @since 1.0.0
129
160
  def save_as_arrow_streaming
130
- save_raw(RecordBatchStreamWriter)
161
+ save_as_arrows
131
162
  end
132
163
 
133
164
  # @deprecated Use `format: :arrow_streaming` instead.
134
165
  def save_as_stream
135
- save_as_arrow_streaming
166
+ save_as_arrows
136
167
  end
137
168
 
138
169
  def csv_save(**options)
@@ -21,51 +21,27 @@ module Arrow
21
21
  # TODO: Almost codes should be implemented in Apache Arrow C++.
22
22
  class TableTableFormatter < TableFormatter
23
23
  private
24
- def format_header(text, columns)
25
- columns.each do |column|
24
+ def format_header(text, column_formatters)
25
+ column_formatters.each do |column_formatter|
26
26
  text << "\t"
27
- text << format_column_name(column)
27
+ text << column_formatter.aligned_name
28
28
  end
29
29
  text << "\n"
30
30
  end
31
31
 
32
- FLOAT_N_DIGITS = 10
33
- def format_column_name(column)
34
- case column.data_type
35
- when TimestampDataType
36
- "%*s" % [::Time.now.iso8601.size, column.name]
37
- when FloatDataType, DoubleDataType
38
- "%*s" % [FLOAT_N_DIGITS, column.name]
39
- else
40
- column.name
41
- end
42
- end
43
-
44
- def format_rows(text, columns, rows, n_digits, start_offset)
32
+ def format_rows(text, column_formatters, rows, n_digits, start_offset)
45
33
  rows.each_with_index do |row, nth_row|
46
34
  text << ("%*d" % [n_digits, start_offset + nth_row])
47
35
  row.each_with_index do |column_value, nth_column|
48
36
  text << "\t"
49
- column = columns[nth_column]
50
- text << format_column_value(column, column_value)
37
+ column_formatter = column_formatters[nth_column]
38
+ aligned_name = column_formatter.aligned_name
39
+ text << column_formatter.format_value(column_value, aligned_name.size)
51
40
  end
52
41
  text << "\n"
53
42
  end
54
43
  end
55
44
 
56
- def format_column_value(column, value)
57
- case value
58
- when ::Time
59
- value.iso8601
60
- when Float
61
- "%*f" % [[column.name.size, FLOAT_N_DIGITS].max, value]
62
- when Integer
63
- "%*d" % [column.name.size, value]
64
- else
65
- "%-*s" % [column.name.size, value.to_s]
66
- end
67
- end
68
-
69
45
  def format_ellipsis(text)
70
46
  text << "...\n"
71
47
  end