scout-gear 7.1.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +65 -2
  3. data/VERSION +1 -1
  4. data/bin/scout +5 -1
  5. data/lib/rbbt-scout.rb +5 -0
  6. data/lib/scout/concurrent_stream.rb +13 -8
  7. data/lib/scout/config.rb +168 -0
  8. data/lib/scout/exceptions.rb +5 -3
  9. data/lib/scout/indiferent_hash/options.rb +1 -0
  10. data/lib/scout/indiferent_hash.rb +4 -2
  11. data/lib/scout/log/color.rb +3 -2
  12. data/lib/scout/log/progress/report.rb +1 -0
  13. data/lib/scout/log/progress/util.rb +66 -1
  14. data/lib/scout/log/progress.rb +5 -3
  15. data/lib/scout/log.rb +3 -2
  16. data/lib/scout/misc/helper.rb +31 -0
  17. data/lib/scout/misc/monitor.rb +4 -1
  18. data/lib/scout/misc/system.rb +15 -0
  19. data/lib/scout/misc.rb +2 -0
  20. data/lib/scout/named_array.rb +68 -0
  21. data/lib/scout/open/stream.rb +58 -33
  22. data/lib/scout/path/find.rb +27 -3
  23. data/lib/scout/path/util.rb +7 -4
  24. data/lib/scout/persist/serialize.rb +7 -14
  25. data/lib/scout/persist.rb +46 -12
  26. data/lib/scout/resource/produce.rb +7 -94
  27. data/lib/scout/resource/software.rb +176 -0
  28. data/lib/scout/semaphore.rb +8 -1
  29. data/lib/scout/tsv/dumper.rb +112 -0
  30. data/lib/scout/tsv/index.rb +161 -0
  31. data/lib/scout/tsv/open.rb +128 -0
  32. data/lib/scout/tsv/parser.rb +230 -30
  33. data/lib/scout/tsv/path.rb +13 -0
  34. data/lib/scout/tsv/persist/adapter.rb +367 -0
  35. data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
  36. data/lib/scout/tsv/persist/serialize.rb +117 -0
  37. data/lib/scout/tsv/persist/tokyocabinet.rb +113 -0
  38. data/lib/scout/tsv/persist.rb +13 -0
  39. data/lib/scout/tsv/traverse.rb +143 -0
  40. data/lib/scout/tsv/util/filter.rb +303 -0
  41. data/lib/scout/tsv/util/process.rb +73 -0
  42. data/lib/scout/tsv/util/select.rb +220 -0
  43. data/lib/scout/tsv/util.rb +82 -0
  44. data/lib/scout/tsv.rb +16 -3
  45. data/lib/scout/work_queue/worker.rb +4 -4
  46. data/lib/scout/work_queue.rb +22 -7
  47. data/lib/scout/workflow/definition.rb +101 -4
  48. data/lib/scout/workflow/step/config.rb +18 -0
  49. data/lib/scout/workflow/step/dependencies.rb +40 -0
  50. data/lib/scout/workflow/step/file.rb +15 -0
  51. data/lib/scout/workflow/step/info.rb +35 -4
  52. data/lib/scout/workflow/step/progress.rb +14 -0
  53. data/lib/scout/workflow/step/provenance.rb +148 -0
  54. data/lib/scout/workflow/step.rb +71 -17
  55. data/lib/scout/workflow/task.rb +10 -5
  56. data/lib/scout/workflow/usage.rb +3 -1
  57. data/lib/scout/workflow.rb +11 -3
  58. data/lib/scout-gear.rb +1 -0
  59. data/lib/scout.rb +1 -0
  60. data/scout-gear.gemspec +64 -10
  61. data/scout_commands/find +1 -1
  62. data/scout_commands/workflow/task +16 -9
  63. data/scout_commands/workflow/task_old +2 -2
  64. data/share/software/install_helpers +523 -0
  65. data/test/scout/log/test_progress.rb +0 -2
  66. data/test/scout/misc/test_system.rb +21 -0
  67. data/test/scout/open/test_stream.rb +160 -1
  68. data/test/scout/path/test_find.rb +14 -7
  69. data/test/scout/resource/test_software.rb +24 -0
  70. data/test/scout/test_config.rb +66 -0
  71. data/test/scout/test_meta_extension.rb +10 -0
  72. data/test/scout/test_named_array.rb +19 -0
  73. data/test/scout/test_persist.rb +96 -0
  74. data/test/scout/test_tmpfile.rb +1 -1
  75. data/test/scout/test_tsv.rb +50 -1
  76. data/test/scout/test_work_queue.rb +41 -13
  77. data/test/scout/tsv/persist/test_adapter.rb +44 -0
  78. data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
  79. data/test/scout/tsv/persist/test_tokyocabinet.rb +92 -0
  80. data/test/scout/tsv/test_dumper.rb +44 -0
  81. data/test/scout/tsv/test_index.rb +156 -0
  82. data/test/scout/tsv/test_open.rb +9 -0
  83. data/test/scout/tsv/test_parser.rb +114 -3
  84. data/test/scout/tsv/test_persist.rb +43 -0
  85. data/test/scout/tsv/test_traverse.rb +116 -0
  86. data/test/scout/tsv/test_util.rb +23 -0
  87. data/test/scout/tsv/util/test_filter.rb +188 -0
  88. data/test/scout/tsv/util/test_process.rb +47 -0
  89. data/test/scout/tsv/util/test_select.rb +44 -0
  90. data/test/scout/work_queue/test_worker.rb +66 -9
  91. data/test/scout/workflow/step/test_dependencies.rb +25 -0
  92. data/test/scout/workflow/step/test_info.rb +15 -17
  93. data/test/scout/workflow/step/test_load.rb +19 -21
  94. data/test/scout/workflow/step/test_provenance.rb +25 -0
  95. data/test/scout/workflow/test_step.rb +206 -10
  96. data/test/scout/workflow/test_task.rb +0 -3
  97. data/test/test_helper.rb +9 -1
  98. metadata +50 -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
@@ -0,0 +1,113 @@
1
+ require 'tokyocabinet'
2
+ require_relative 'adapter'
3
+
4
+ module ScoutCabinet
5
+ attr_accessor :persistence_path, :persistence_class
6
+
7
+ def self.open(path, write, tokyocabinet_class = TokyoCabinet::HDB)
8
+ path = path.find if Path === path
9
+ if String === tokyocabinet_class && tokyocabinet_class.include?(":big")
10
+ big = true
11
+ tokyocabinet_class = tokyocabinet_class.split(":").first
12
+ else
13
+ big = false
14
+ end
15
+
16
+ dir = File.dirname(File.expand_path(path))
17
+ Open.mkdir(dir) unless File.exist?(dir)
18
+
19
+ tokyocabinet_class = TokyoCabinet::HDB if tokyocabinet_class == "HDB" or tokyocabinet_class.nil?
20
+ tokyocabinet_class = TokyoCabinet::BDB if tokyocabinet_class == "BDB"
21
+
22
+ database = Persist::CONNECTIONS[path] ||= tokyocabinet_class.new
23
+
24
+ if big and not Open.exists?(path)
25
+ database.tune(nil,nil,nil,tokyocabinet_class::TLARGE | tokyocabinet_class::TDEFLATE)
26
+ end
27
+
28
+ flags = (write ? tokyocabinet_class::OWRITER | tokyocabinet_class::OCREAT : tokyocabinet_class::OREADER)
29
+ database.close
30
+
31
+ if !database.open(path, flags)
32
+ ecode = database.ecode
33
+ raise "Open error: #{database.errmsg(ecode)}. Trying to open file #{path}"
34
+ end
35
+
36
+ database.extend ScoutCabinet
37
+ database.persistence_path ||= path
38
+ database.persistence_class = tokyocabinet_class
39
+
40
+ database.open(path, tokyocabinet_class::OREADER)
41
+
42
+ Persist::CONNECTIONS[path] = database
43
+
44
+ database
45
+ end
46
+
47
+ def close
48
+ @closed = true
49
+ @writable = false
50
+ super
51
+ end
52
+
53
+ def read(force = false)
54
+ return if ! write? && ! closed && ! force
55
+ self.close
56
+ if !self.open(@persistence_path, persistence_class::OREADER)
57
+ ecode = self.ecode
58
+ raise "Open error: #{self.errmsg(ecode)}. Trying to open file #{@persistence_path}"
59
+ end
60
+
61
+ @writable = false
62
+ @closed = false
63
+
64
+ self
65
+ end
66
+
67
+ def write(force = true)
68
+ return if write? && ! closed && ! force
69
+ self.close
70
+
71
+ if !self.open(@persistence_path, persistence_class::OWRITER)
72
+ ecode = self.ecode
73
+ raise "Open error: #{self.errmsg(ecode)}. Trying to open file #{@persistence_path}"
74
+ end
75
+
76
+ @writable = true
77
+ @closed = false
78
+
79
+ self
80
+ end
81
+
82
+ #def self.open_tokyocabinet(path, write, serializer = nil, tokyocabinet_class = TokyoCabinet::HDB)
83
+ # raise
84
+ # write = true unless File.exist? path
85
+
86
+ # FileUtils.mkdir_p File.dirname(path) unless File.exist?(File.dirname(path))
87
+
88
+ # database = Persist::TCAdapter.open(path, write, tokyocabinet_class)
89
+
90
+ # unless serializer == :clean
91
+ # TSV.setup database
92
+ # database.write_and_read do
93
+ # database.serializer = serializer
94
+ # end if serializer && database.serializer != serializer
95
+ # end
96
+
97
+ # database
98
+ #end
99
+ end
100
+
101
+ Persist.save_drivers[:HDB] = proc do |file, content|
102
+ data = ScoutCabinet.open(file, true, "HDB")
103
+ content.annotate(data)
104
+ data.extend TSVAdapter
105
+ data.merge!(content)
106
+ data
107
+ end
108
+
109
+ Persist.load_drivers[:HDB] = proc do |file|
110
+ data = ScoutCabinet.open(file, false, "HDB")
111
+ data.extend TSVAdapter unless TSVAdapter === data
112
+ data
113
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'persist/adapter'
2
+ require_relative 'persist/tokyocabinet'
3
+
4
+ Persist.save_drivers[:tsv] = proc do |file,content|
5
+ stream = if IO === content
6
+ content
7
+ elsif content.respond_to?(:stream)
8
+ content.stream
9
+ end
10
+ Open.sensible_write(file, stream)
11
+ end
12
+
13
+ Persist.load_drivers[:tsv] = proc do |file| TSV.open file end
@@ -0,0 +1,143 @@
1
+ require_relative 'parser'
2
+ module TSV
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
+ end
12
+
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
98
+ end
99
+ end
100
+ end
101
+ else
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
135
+ end
136
+ end
137
+ end
138
+
139
+ [key_name, field_names]
140
+ end
141
+
142
+ alias through traverse
143
+ end