scout-gear 7.2.0 → 7.3.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 (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