scout-gear 7.2.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +37 -3
  3. data/VERSION +1 -1
  4. data/lib/scout/concurrent_stream.rb +9 -8
  5. data/lib/scout/exceptions.rb +1 -0
  6. data/lib/scout/log/color.rb +0 -1
  7. data/lib/scout/log/progress/util.rb +65 -0
  8. data/lib/scout/misc/helper.rb +31 -0
  9. data/lib/scout/misc/monitor.rb +1 -1
  10. data/lib/scout/misc.rb +1 -0
  11. data/lib/scout/open/stream.rb +21 -27
  12. data/lib/scout/persist.rb +42 -28
  13. data/lib/scout/semaphore.rb +8 -1
  14. data/lib/scout/tsv/dumper.rb +13 -8
  15. data/lib/scout/tsv/index.rb +127 -15
  16. data/lib/scout/tsv/open.rb +128 -0
  17. data/lib/scout/tsv/parser.rb +70 -43
  18. data/lib/scout/tsv/path.rb +4 -4
  19. data/lib/scout/tsv/persist/adapter.rb +52 -33
  20. data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
  21. data/lib/scout/tsv/persist/serialize.rb +117 -0
  22. data/lib/scout/tsv/persist/tokyocabinet.rb +3 -3
  23. data/lib/scout/tsv/persist.rb +0 -2
  24. data/lib/scout/tsv/traverse.rb +130 -35
  25. data/lib/scout/tsv/util/filter.rb +303 -0
  26. data/lib/scout/tsv/util/process.rb +73 -0
  27. data/lib/scout/tsv/util/select.rb +220 -0
  28. data/lib/scout/tsv/util.rb +77 -19
  29. data/lib/scout/tsv.rb +2 -2
  30. data/lib/scout/work_queue/worker.rb +1 -1
  31. data/lib/scout/workflow/definition.rb +8 -0
  32. data/lib/scout/workflow/step/info.rb +4 -0
  33. data/lib/scout/workflow/step/progress.rb +14 -0
  34. data/lib/scout/workflow/step.rb +10 -5
  35. data/lib/scout/workflow/task.rb +8 -4
  36. data/lib/scout/workflow/usage.rb +2 -0
  37. data/scout-gear.gemspec +33 -10
  38. data/scout_commands/workflow/task +3 -2
  39. data/scout_commands/workflow/task_old +2 -2
  40. data/test/scout/open/test_stream.rb +1 -1
  41. data/test/scout/test_persist.rb +61 -0
  42. data/test/scout/test_tmpfile.rb +1 -1
  43. data/test/scout/test_tsv.rb +10 -1
  44. data/test/scout/test_work_queue.rb +1 -0
  45. data/test/scout/tsv/persist/test_adapter.rb +10 -0
  46. data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
  47. data/test/scout/tsv/test_index.rb +94 -2
  48. data/test/scout/tsv/test_open.rb +9 -0
  49. data/test/scout/tsv/test_parser.rb +28 -3
  50. data/test/scout/tsv/test_persist.rb +7 -0
  51. data/test/scout/tsv/test_traverse.rb +110 -3
  52. data/test/scout/tsv/test_util.rb +23 -0
  53. data/test/scout/tsv/util/test_filter.rb +188 -0
  54. data/test/scout/tsv/util/test_process.rb +47 -0
  55. data/test/scout/tsv/util/test_select.rb +44 -0
  56. data/test/scout/work_queue/test_worker.rb +63 -6
  57. data/test/scout/workflow/step/test_load.rb +3 -3
  58. data/test/scout/workflow/test_step.rb +10 -10
  59. data/test/test_helper.rb +3 -1
  60. metadata +19 -6
@@ -0,0 +1,324 @@
1
+ class FixWidthTable
2
+
3
+ attr_accessor :filename, :file, :value_size, :record_size, :range, :size, :mask, :write
4
+ def initialize(filename, value_size = nil, range = nil, update = false, in_memory = true)
5
+ @filename = filename
6
+
7
+ if update || %w(memory stringio).include?(filename.to_s.downcase) || ! File.exist?(filename)
8
+ Log.debug "FixWidthTable create: #{ filename }"
9
+ @value_size = value_size
10
+ @range = range
11
+ @record_size = @value_size + (@range ? 16 : 8)
12
+ @write = true
13
+
14
+ if %w(memory stringio).include?(filename.to_s.downcase)
15
+ @filename = :memory
16
+ @file = StringIO.new
17
+ else
18
+ FileUtils.rm @filename if File.exist? @filename
19
+ FileUtils.mkdir_p File.dirname(@filename) unless File.exist? @filename
20
+ @file = File.open(@filename, 'w:ASCII-8BIT')
21
+ end
22
+
23
+ @file.write [value_size].pack("L")
24
+ @file.write [@range ? 1 : 0 ].pack("C")
25
+
26
+ @size = 0
27
+ else
28
+ Log.debug "FixWidthTable up-to-date: #{ filename } - (in_memory:#{in_memory})"
29
+ if in_memory
30
+ @file = Open.open(@filename, :mode => 'r:ASCII-8BIT'){|f| StringIO.new f.read}
31
+ else
32
+ @file = File.open(@filename, 'r:ASCII-8BIT')
33
+ end
34
+ @value_size = @file.read(4).unpack("L").first
35
+ @range = @file.read(1).unpack("C").first == 1
36
+ @record_size = @value_size + (@range ? 16 : 8)
37
+ @write = false
38
+
39
+ @size = (File.size(@filename) - 5) / (@record_size)
40
+ end
41
+
42
+ @mask = "a#{@value_size}"
43
+ end
44
+
45
+ def write?
46
+ @write
47
+ end
48
+
49
+ def persistence_path
50
+ @filename
51
+ end
52
+
53
+ def persistence_path=(value)
54
+ @filename=value
55
+ end
56
+
57
+ def self.get(filename, value_size = nil, range = nil, update = false)
58
+ return self.new(filename, value_size, range, update) if filename == :memory
59
+ case
60
+ when (!File.exist?(filename) or update or not Persist::CONNECTIONS.include?(filename))
61
+ Persist::CONNECTIONS[filename] = self.new(filename, value_size, range, update)
62
+ end
63
+
64
+ Persist::CONNECTIONS[filename]
65
+ end
66
+
67
+ def format(pos, value)
68
+ padding = value_size - value.length
69
+ if @range
70
+ (pos + [padding, value + ("\0" * padding)]).pack("llll#{mask}")
71
+ else
72
+ [pos, padding, value + ("\0" * padding)].pack("ll#{mask}")
73
+ end
74
+ end
75
+
76
+ def add(pos, value)
77
+ format = format(pos, value)
78
+ @file.write format
79
+
80
+ @size += 1
81
+ end
82
+
83
+ def last_pos
84
+ pos(size - 1)
85
+ end
86
+
87
+ def idx_pos(index)
88
+ return nil if index < 0 or index >= size
89
+ @file.seek(5 + (record_size) * index, IO::SEEK_SET)
90
+ @file.read(4).unpack("l").first
91
+ end
92
+
93
+ def idx_pos_end(index)
94
+ return nil if index < 0 or index >= size
95
+ @file.seek(9 + (record_size) * index, IO::SEEK_SET)
96
+ @file.read(4).unpack("l").first
97
+ end
98
+
99
+ def idx_overlap(index)
100
+ return nil if index < 0 or index >= size
101
+ @file.seek(13 + (record_size) * index, IO::SEEK_SET)
102
+ @file.read(4).unpack("l").first
103
+ end
104
+
105
+ def idx_value(index)
106
+ return nil if index < 0 or index >= size
107
+ @file.seek((@range ? 17 : 9 ) + (record_size) * index, IO::SEEK_SET)
108
+ padding = @file.read(4).unpack("l").first+1
109
+ txt = @file.read(value_size)
110
+ str = txt.unpack(mask).first
111
+ padding > 1 ? str[0..-padding] : str
112
+ end
113
+
114
+ def read(force = false)
115
+ return if @filename == :memory
116
+ @write = false
117
+ @file.close unless @file.closed?
118
+ @file = File.open(filename, 'r:ASCII-8BIT')
119
+ end
120
+
121
+ def close
122
+ @write = false
123
+ @file.close
124
+ end
125
+
126
+ def dump
127
+ read
128
+ @file.rewind
129
+ @file.read
130
+ end
131
+
132
+ #{{{ Adding data
133
+
134
+ def add_point(data)
135
+ data.sort_by{|value,pos| pos }.each do |value, pos|
136
+ add pos, value
137
+ end
138
+ end
139
+
140
+ def add_range_point(pos, value)
141
+ @latest ||= []
142
+ while @latest.any? and @latest[0] < pos[0]
143
+ @latest.shift
144
+ end
145
+ overlap = @latest.length
146
+ add pos + [overlap], value
147
+ @latest << pos[1]
148
+ end
149
+
150
+ def add_range(data)
151
+ @latest = []
152
+ data.sort_by{|value, pos| pos[0] }.each do |value, pos|
153
+ add_range_point(pos, value)
154
+ end
155
+ end
156
+
157
+ #{{{ Searching
158
+
159
+ def closest(pos)
160
+ upper = size - 1
161
+ lower = 0
162
+
163
+ return -1 if upper < lower
164
+
165
+ while(upper >= lower) do
166
+ idx = lower + (upper - lower) / 2
167
+ pos_idx = idx_pos(idx)
168
+
169
+ case pos <=> pos_idx
170
+ when 0
171
+ break
172
+ when -1
173
+ upper = idx - 1
174
+ when 1
175
+ lower = idx + 1
176
+ end
177
+ end
178
+
179
+ if pos_idx > pos
180
+ idx = idx - 1
181
+ end
182
+
183
+ idx.to_i
184
+ end
185
+
186
+ def get_range(pos, return_idx = false)
187
+ case pos
188
+ when Range
189
+ r_start = pos.begin
190
+ r_end = pos.end
191
+ when Array
192
+ r_start, r_end = pos
193
+ else
194
+ r_start, r_end = pos, pos
195
+ end
196
+
197
+ idx = closest(r_start)
198
+
199
+ return [] if idx >= size
200
+ return [] if idx < 0 and r_start == r_end
201
+
202
+ idx = 0 if idx < 0
203
+
204
+ overlap = idx_overlap(idx)
205
+
206
+ idx -= overlap unless overlap.nil?
207
+
208
+ values = []
209
+ l_start = idx_pos(idx)
210
+ l_end = idx_pos_end(idx)
211
+
212
+ if return_idx
213
+ while l_start <= r_end
214
+ values << idx if l_end >= r_start
215
+ idx += 1
216
+ break if idx >= size
217
+ l_start = idx_pos(idx)
218
+ l_end = idx_pos_end(idx)
219
+ end
220
+ else
221
+ while l_start <= r_end
222
+ values << idx_value(idx) if l_end >= r_start
223
+ idx += 1
224
+ break if idx >= size
225
+ l_start = idx_pos(idx)
226
+ l_end = idx_pos_end(idx)
227
+ end
228
+ end
229
+
230
+ values
231
+ end
232
+
233
+ def get_point(pos, return_idx = false)
234
+ if Range === pos
235
+ r_start = pos.begin
236
+ r_end = pos.end
237
+ else
238
+ r_start = pos.to_i
239
+ r_end = pos.to_i
240
+ end
241
+
242
+ idx = closest(r_start)
243
+
244
+ return [] if idx >= size
245
+ return [] if idx < 0 and r_start == r_end
246
+
247
+ idx = 0 if idx < 0
248
+
249
+ idx += 1 unless idx_pos(idx) >= r_start
250
+
251
+ return [] if idx >= size
252
+
253
+ values = []
254
+ l_start = idx_pos(idx)
255
+ l_end = idx_pos_end(idx)
256
+ if return_idx
257
+ while l_start <= r_end
258
+ values << idx
259
+ idx += 1
260
+ break if idx >= size
261
+ l_start = idx_pos(idx)
262
+ l_end = idx_pos_end(idx)
263
+ end
264
+ else
265
+ while l_start <= r_end
266
+ values << idx_value(idx)
267
+ idx += 1
268
+ break if idx >= size
269
+ l_start = idx_pos(idx)
270
+ l_end = idx_pos_end(idx)
271
+ end
272
+ end
273
+
274
+ values
275
+ end
276
+
277
+ def [](pos)
278
+ return [] if size == 0
279
+ self.read
280
+ if @range
281
+ get_range(pos)
282
+ else
283
+ get_point(pos)
284
+ end
285
+ end
286
+
287
+ def overlaps(pos, value = false)
288
+ return [] if size == 0
289
+ idxs = if @range
290
+ get_range(pos, true)
291
+ else
292
+ get_point(pos, true)
293
+ end
294
+ if value
295
+ idxs.collect{|idx| [idx_pos(idx), idx_pos_end(idx), idx_value(idx)] * ":"}
296
+ else
297
+ idxs.collect{|idx| [idx_pos(idx), idx_pos_end(idx)] * ":"}
298
+ end
299
+ end
300
+
301
+
302
+ def values_at(*list)
303
+ list.collect{|pos|
304
+ self[pos]
305
+ }
306
+ end
307
+
308
+ def chunked_values_at(keys, max = 5000)
309
+ Misc.ordered_divide(keys, max).inject([]) do |acc,c|
310
+ new = self.values_at(*c)
311
+ new.annotate acc if new.respond_to? :annotate and acc.empty?
312
+ acc.concat(new)
313
+ end
314
+ end
315
+ end
316
+
317
+ Persist.save_drivers[:fwt] = proc do |file, content|
318
+ content.file.seek 0
319
+ Misc.sensiblewrite(file, content.file.read)
320
+ end
321
+
322
+ Persist.load_drivers[:fwt] = proc do |file|
323
+ FixWidthTable.new file
324
+ end
@@ -0,0 +1,117 @@
1
+ require 'base64'
2
+ module TSVAdapter
3
+
4
+ class CleanSerializer
5
+ def self.dump(o); o end
6
+ def self.load(o); o end
7
+ end
8
+
9
+ class BinarySerializer
10
+ def self.dump(o); [o].pack('m'); end
11
+ def self.load(str); str.unpack('m').first; end
12
+ end
13
+
14
+ class IntegerSerializer
15
+ def self.dump(i); [i].pack("l"); end
16
+ def self.load(str); str.unpack("l").first; end
17
+ end
18
+
19
+ class FloatSerializer
20
+ def self.dump(i); [i].pack("d"); end
21
+ def self.load(str); str.unpack("d").first; end
22
+ end
23
+
24
+ class StrictIntegerArraySerializer
25
+ def self.dump(a); a.pack("l*"); end
26
+ def self.load(str); a = str.unpack("l*"); end
27
+ end
28
+
29
+ class StrictFloatArraySerializer
30
+ def self.dump(a); a.pack("d*"); end
31
+ def self.load(str); a = str.unpack("d*"); end
32
+ end
33
+
34
+ class IntegerArraySerializer
35
+ NIL_INT = -999
36
+ def self.dump(a); a.collect{|v| v || NIL_INT}.pack("l*"); end
37
+ def self.load(str); a = str.unpack("l*"); a.collect{|v| v == NIL_INT ? nil : v}; end
38
+ end
39
+
40
+ class FloatArraySerializer
41
+ NIL_FLOAT = -999.999
42
+ def self.dump(a); a.collect{|v| v || NIL_FLOAT}.pack("d*"); end
43
+ def self.load(str); a = str.unpack("d*"); a.collect{|v| v == NIL_FLOAT ? nil : v}; end
44
+ end
45
+
46
+ class StringSerializer
47
+ def self.dump(str); str.to_s; end
48
+ def self.load(str); str.dup; end
49
+ end
50
+
51
+ class StringArraySerializer
52
+ def self.dump(array)
53
+ array.collect{|a| a.to_s} * "\t"
54
+ end
55
+
56
+ def self.load(string)
57
+ return nil if string.nil? or string == 'nil'
58
+ return [] if string.empty?
59
+ string.split("\t", -1)
60
+ end
61
+ end
62
+
63
+ class StringDoubleArraySerializer
64
+ def self.dump(array)
65
+ begin
66
+ array.collect{|a| a.collect{|a| a.to_s } * "|"} * "\t"
67
+ rescue Encoding::CompatibilityError
68
+ array.collect{|a| a.collect{|a| a.to_s.force_encoding('UTF-8')} * "|"} * "\t"
69
+ end
70
+ end
71
+
72
+ def self.load(string)
73
+ return [] if string.nil?
74
+ string.split("\t", -1).collect{|l| l.split("|", -1)}
75
+ end
76
+ end
77
+
78
+ class TSVMarshalSerializer
79
+ def self.dump(tsv)
80
+ Marshal.dump(tsv.dup)
81
+ end
82
+
83
+ def self.load(string)
84
+ TSV.setup Marshal.load(string)
85
+ end
86
+ end
87
+
88
+ class TSVSerializer
89
+ def self.dump(tsv)
90
+ tsv.to_s
91
+ end
92
+
93
+ def self.load(string)
94
+ TSV.open StringIO.new(string)
95
+ end
96
+ end
97
+
98
+ SERIALIZER_ALIAS = {
99
+ :single => StringSerializer,
100
+ :list => StringArraySerializer,
101
+ :flat => StringArraySerializer,
102
+ :double => StringDoubleArraySerializer,
103
+ :clean => CleanSerializer,
104
+ :integer => IntegerSerializer,
105
+ :float => FloatSerializer,
106
+ :integer_array => IntegerArraySerializer,
107
+ :float_array => FloatArraySerializer,
108
+ :strict_integer_array => StrictIntegerArraySerializer,
109
+ :strict_float_array => StrictFloatArraySerializer,
110
+ :marshal => Marshal,
111
+ :string => StringSerializer,
112
+ :binary => BinarySerializer,
113
+ :tsv => TSVSerializer,
114
+ :marshal_tsv => TSVMarshalSerializer
115
+ }
116
+
117
+ end
@@ -4,8 +4,8 @@ require_relative 'adapter'
4
4
  module ScoutCabinet
5
5
  attr_accessor :persistence_path, :persistence_class
6
6
 
7
- CONNECTIONS = {}
8
7
  def self.open(path, write, tokyocabinet_class = TokyoCabinet::HDB)
8
+ path = path.find if Path === path
9
9
  if String === tokyocabinet_class && tokyocabinet_class.include?(":big")
10
10
  big = true
11
11
  tokyocabinet_class = tokyocabinet_class.split(":").first
@@ -19,7 +19,7 @@ module ScoutCabinet
19
19
  tokyocabinet_class = TokyoCabinet::HDB if tokyocabinet_class == "HDB" or tokyocabinet_class.nil?
20
20
  tokyocabinet_class = TokyoCabinet::BDB if tokyocabinet_class == "BDB"
21
21
 
22
- database = CONNECTIONS[path] ||= tokyocabinet_class.new
22
+ database = Persist::CONNECTIONS[path] ||= tokyocabinet_class.new
23
23
 
24
24
  if big and not Open.exists?(path)
25
25
  database.tune(nil,nil,nil,tokyocabinet_class::TLARGE | tokyocabinet_class::TDEFLATE)
@@ -39,7 +39,7 @@ module ScoutCabinet
39
39
 
40
40
  database.open(path, tokyocabinet_class::OREADER)
41
41
 
42
- CONNECTIONS[path] = database
42
+ Persist::CONNECTIONS[path] = database
43
43
 
44
44
  database
45
45
  end
@@ -4,8 +4,6 @@ require_relative 'persist/tokyocabinet'
4
4
  Persist.save_drivers[:tsv] = proc do |file,content|
5
5
  stream = if IO === content
6
6
  content
7
- elsif content.respond_to?(:get_stream)
8
- content.get_stream
9
7
  elsif content.respond_to?(:stream)
10
8
  content.stream
11
9
  end
@@ -1,48 +1,143 @@
1
1
  require_relative 'parser'
2
2
  module TSV
3
- def self.traverse_add(into, res)
4
- case into
5
- when TSV::Dumper
6
- into.add *res
7
- when TSV, Hash
8
- key, value = res
9
- into[key] = value
10
- end
3
+ def self.identify_field(key_field, fields, name)
4
+ return :key if name == :key || key_field.start_with?(name.to_s)
5
+ name.collect!{|n| key_field == n ? :key : n } if Array === name
6
+ NamedArray.identify_name(fields, name)
7
+ end
8
+
9
+ def identify_field(name)
10
+ TSV.identify_field(@key_field, @fields, name)
11
11
  end
12
12
 
13
- def self.traverse(obj, into: nil, cpus: nil, bar: nil, **options, &block)
14
- case obj
15
- when TSV
16
- self.traverse(obj.stream, into: into, cpus: cpus, bar: bar, **options, &block)
17
- when String
18
- f = Open.open(obj)
19
- self.traverse(f, into: into, cpus: cpus, bar: bar, **options, &block)
20
- when Step
21
- self.traverse(obj.get_stream, into: into, cpus: cpus, bar: bar, **options, &block)
22
- when IO
23
- if into
24
- into_thread = Thread.new do
25
- Thread.current.report_on_exception = false
26
- Thread.current["name"] = "Traverse into"
27
- TSV.parse obj, **options do |k,v|
28
- begin
29
- res = block.call k, v
30
- traverse_add into, res
31
- rescue
32
- into.abort $!
13
+ def traverse(key_field_pos = :key, fields_pos = nil, type: nil, one2one: false, unnamed: false, key_field: nil, fields: nil, &block)
14
+ key_field = key_field_pos if key_field.nil?
15
+ fields = fields_pos if fields.nil?
16
+ type = @type if type.nil?
17
+ key_pos = self.identify_field(key_field)
18
+ fields = [fields] unless fields.nil? || Array === fields
19
+ positions = fields.nil? ? nil : self.identify_field(fields)
20
+
21
+ if key_pos == :key
22
+ key_name = @key_field
23
+ else
24
+ key_name = @fields[key_pos]
25
+ if positions.nil?
26
+ positions = (0..@fields.length-1).to_a
27
+ positions.delete_at key_pos
28
+ positions.unshift :key
29
+ end
30
+ end
31
+
32
+ if positions.nil? && key_pos == :key
33
+ field_names = @fields
34
+ elsif positions.nil? && key_pos != :key
35
+ field_names = @fields.dup
36
+ field_names.delete_at key_pos
37
+ elsif positions.include?(:key)
38
+ field_names = positions.collect{|p| p == :key ? @key_field : @fields[p] }
39
+ else
40
+ field_names = @fields.values_at *positions
41
+ end
42
+
43
+ key_index = positions.index :key if positions
44
+ positions.delete :key if positions
45
+
46
+ each do |key,values|
47
+ values = [values] if @type == :single
48
+ if positions.nil?
49
+ if key_pos != :key
50
+ values = values.dup
51
+ key = values.delete_at(key_pos)
52
+ end
53
+ else
54
+ orig_key = key
55
+ key = values[key_pos] if key_pos != :key
56
+
57
+ values = values.values_at(*positions)
58
+ if key_index
59
+ if @type == :double
60
+ values.insert key_index, [orig_key]
61
+ else
62
+ values.insert key_index, orig_key
63
+ end
64
+ end
65
+ end
66
+
67
+ if Array === key
68
+ if @type == :double && one2one
69
+ if one2one == :fill
70
+ key.each_with_index do |key_i,i|
71
+ if type == :double
72
+ v_i = values.collect{|v| [v[i] || v.first] }
73
+ else
74
+ v_i = values.collect{|v| v[i] || v.first }
75
+ end
76
+ yield key_i, v_i
77
+ end
78
+ else
79
+ key.each_with_index do |key_i,i|
80
+ if type == :double
81
+ v_i = values.collect{|v| [v[i]] }
82
+ else
83
+ v_i = values.collect{|v| v[i] }
84
+ end
85
+ yield key_i, v_i
86
+ end
87
+ end
88
+ else
89
+ key.each_with_index do |key_i, i|
90
+ if type == :double
91
+ yield key_i, values
92
+ elsif type == :list
93
+ yield key_i, values.collect{|v| v[i] }
94
+ elsif type == :flat
95
+ yield key_i, values.flatten
96
+ elsif type == :single
97
+ yield key_i, values.first
33
98
  end
34
- nil
35
99
  end
36
- into.close if into.respond_to?(:close)
37
100
  end
38
- Thread.pass until into_thread
39
- into
40
101
  else
41
- TSV.parse obj, **options do |k,v|
42
- block.call k, v
43
- nil
102
+ if type == @type
103
+ if type == :single
104
+ yield key, values.first
105
+ else
106
+ yield key, values
107
+ end
108
+ else
109
+ case [type, @type]
110
+ when [:double, :list]
111
+ yield key, values.collect{|v| [v] }
112
+ when [:double, :flat]
113
+ yield key, [values]
114
+ when [:double, :single]
115
+ yield key, [values]
116
+ when [:list, :double]
117
+ yield key, values.collect{|v| v.first }
118
+ when [:list, :flat]
119
+ yield key, [values.first]
120
+ when [:list, :single]
121
+ yield key, values
122
+ when [:flat, :double]
123
+ yield key, values.flatten
124
+ when [:flat, :list]
125
+ yield key, values.flatten
126
+ when [:flat, :single]
127
+ yield key, values
128
+ when [:single, :double]
129
+ yield key, values.flatten.first
130
+ when [:single, :list]
131
+ yield key, values.first
132
+ when [:single, :flat]
133
+ yield key, values.first
134
+ end
44
135
  end
45
136
  end
46
137
  end
138
+
139
+ [key_name, field_names]
47
140
  end
141
+
142
+ alias through traverse
48
143
  end