rbbt-util 2.1.0 → 3.0.2
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/bin/rbbt_query.rb +63 -0
- data/lib/rbbt-util.rb +5 -5
- data/lib/rbbt.rb +2 -11
- data/lib/rbbt/util/cmd.rb +1 -1
- data/lib/rbbt/util/fix_width_table.rb +9 -3
- data/lib/rbbt/util/log.rb +23 -7
- data/lib/rbbt/util/misc.rb +121 -15
- data/lib/rbbt/util/open.rb +14 -4
- data/lib/rbbt/util/persistence.rb +52 -21
- data/lib/rbbt/util/rake.rb +108 -21
- data/lib/rbbt/util/resource.rb +338 -0
- data/lib/rbbt/util/simpleDSL.rb +1 -1
- data/lib/rbbt/util/simpleopt.rb +1 -1
- data/lib/rbbt/util/task.rb +340 -0
- data/lib/rbbt/util/tc_hash.rb +19 -2
- data/lib/rbbt/util/tsv.rb +15 -10
- data/lib/rbbt/util/tsv/accessor.rb +16 -7
- data/lib/rbbt/util/tsv/attach.rb +220 -17
- data/lib/rbbt/util/tsv/index.rb +6 -1
- data/lib/rbbt/util/tsv/manipulate.rb +4 -5
- data/lib/rbbt/util/tsv/parse.rb +45 -21
- data/lib/rbbt/util/tsv/resource.rb +74 -0
- data/lib/rbbt/util/workflow.rb +99 -75
- data/test/rbbt/util/test_filecache.rb +2 -2
- data/test/rbbt/util/test_misc.rb +7 -2
- data/test/rbbt/util/test_persistence.rb +40 -5
- data/test/rbbt/util/test_resource.rb +92 -0
- data/test/rbbt/util/test_task.rb +118 -0
- data/test/rbbt/util/test_tsv.rb +5 -1
- data/test/rbbt/util/test_workflow.rb +77 -62
- data/test/rbbt/util/tsv/test_attach.rb +95 -7
- data/test/rbbt/util/tsv/test_index.rb +0 -1
- data/test/rbbt/util/tsv/test_manipulate.rb +20 -0
- data/test/rbbt/util/tsv/test_resource.rb +9 -0
- data/test/test_helper.rb +10 -0
- data/test/test_rbbt.rb +2 -37
- metadata +16 -18
- data/lib/rbbt/util/data_module.rb +0 -93
- data/lib/rbbt/util/path.rb +0 -155
- data/lib/rbbt/util/pkg_config.rb +0 -78
- data/lib/rbbt/util/pkg_data.rb +0 -119
- data/lib/rbbt/util/pkg_software.rb +0 -145
- data/test/rbbt/util/test_data_module.rb +0 -50
- data/test/rbbt/util/test_path.rb +0 -10
- data/test/rbbt/util/test_pkg_data.rb +0 -129
- data/test/test_pkg.rb +0 -28
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rbbt/util/resource'
|
1
2
|
require 'rbbt/util/misc'
|
2
3
|
|
3
4
|
class TSV
|
@@ -68,12 +69,12 @@ class TSV
|
|
68
69
|
when (TSV === identifiers.first or identifiers.empty?)
|
69
70
|
identifiers
|
70
71
|
when
|
71
|
-
identifiers.collect{|f| Path.path(f,
|
72
|
+
identifiers.collect{|f| Resource::Path.path(f, nil, namespace)}
|
72
73
|
end
|
73
74
|
when (identifiers and not Array === identifiers)
|
74
|
-
[Path.path(identifiers,
|
75
|
+
[Resource::Path.path(identifiers, nil, namespace)]
|
75
76
|
when filename
|
76
|
-
Path.path(filename,
|
77
|
+
Resource::Path.path(filename, nil, namespace).identifier_files
|
77
78
|
else
|
78
79
|
[]
|
79
80
|
end
|
@@ -176,7 +177,10 @@ class TSV
|
|
176
177
|
if String === value && value =~ /__Ref:(.*)/
|
177
178
|
return self[$1]
|
178
179
|
else
|
179
|
-
|
180
|
+
|
181
|
+
if Array === value and fields
|
182
|
+
value = NamedArray.name value, fields
|
183
|
+
end
|
180
184
|
value
|
181
185
|
end
|
182
186
|
end
|
@@ -245,13 +249,18 @@ class TSV
|
|
245
249
|
end
|
246
250
|
|
247
251
|
def include?(key)
|
248
|
-
data.include? key
|
252
|
+
@data.include? key
|
249
253
|
end
|
250
254
|
|
251
|
-
def to_s(keys = nil)
|
255
|
+
def to_s(keys = nil, no_options = false)
|
256
|
+
if FalseClass === keys or TrueClass === keys
|
257
|
+
no_options = keys
|
258
|
+
keys = nil
|
259
|
+
end
|
260
|
+
|
252
261
|
str = ""
|
253
262
|
|
254
|
-
str << "#: " << Misc.hash2string(EXTRA_ACCESSORS.collect{|key| [key, self.send(key)]}) << "\n"
|
263
|
+
str << "#: " << Misc.hash2string(EXTRA_ACCESSORS.collect{|key| [key, self.send(key)]}) << "\n" unless no_options
|
255
264
|
if fields
|
256
265
|
str << "#" << key_field << "\t" << fields * "\t" << "\n"
|
257
266
|
end
|
data/lib/rbbt/util/tsv/attach.rb
CHANGED
@@ -1,5 +1,182 @@
|
|
1
1
|
class TSV
|
2
|
+
def self.paste_merge(file1, file2, output, sep = "\t")
|
3
|
+
case
|
4
|
+
when (String === file1 and not file1.index("\n") and file1.length < 250 and File.exists?(file1))
|
5
|
+
file1 = CMD.cmd("sort -k1,1 -t'#{sep}' #{ file1 } | grep -v '^#{sep}' ", :pipe => true)
|
6
|
+
when (String === file1 or StringIO === file1)
|
7
|
+
file1 = CMD.cmd("sort -k1,1 -t'#{sep}' | grep -v '^#{sep}'", :in => file1, :pipe => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
case
|
11
|
+
when (String === file2 and not file2.index("\n") and file2.length < 250 and File.exists?(file2))
|
12
|
+
file2 = CMD.cmd("sort -k1,1 -t'#{sep}' #{ file2 } | grep -v '^#{sep}' ", :pipe => true)
|
13
|
+
when (String === file2 or StringIO === file2)
|
14
|
+
file2 = CMD.cmd("sort -k1,1 -t'#{sep}' | grep -v '^#{sep}'", :in => file2, :pipe => true)
|
15
|
+
end
|
16
|
+
|
17
|
+
output = File.open(output, 'w') if String === output
|
18
|
+
|
19
|
+
cols1 = nil
|
20
|
+
cols2 = nil
|
21
|
+
|
22
|
+
done1 = false
|
23
|
+
done2 = false
|
24
|
+
|
25
|
+
key1 = key2 = nil
|
26
|
+
while key1.nil?
|
27
|
+
while (line1 = file1.gets) =~ /#/; end
|
28
|
+
key1, *parts1 = line1.sub("\n",'').split(sep, -1)
|
29
|
+
cols1 = parts1.length
|
30
|
+
end
|
31
|
+
|
32
|
+
while key2.nil?
|
33
|
+
while (line2 = file2.gets) =~ /#/; end
|
34
|
+
key2, *parts2 = line2.sub("\n",'').split(sep, -1)
|
35
|
+
cols2 = parts2.length
|
36
|
+
end
|
37
|
+
|
38
|
+
key = key1 < key2 ? key1 : key2
|
39
|
+
parts = [""] * (cols1 + cols2)
|
40
|
+
while not (done1 and done2)
|
41
|
+
while (not done1 and key1 == key)
|
42
|
+
parts1.each_with_index do |part, i|
|
43
|
+
parts[i] = (parts[i].nil? or parts[i].empty?) ? part : parts[i] << "|" << part
|
44
|
+
end
|
45
|
+
key1 = nil
|
46
|
+
while key1.nil? and not done1
|
47
|
+
if file1.eof?; done1 = true; else key1, *parts1 = file1.gets.sub("\n",'').split(sep, -1) end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
while (not done2 and key2 == key)
|
51
|
+
parts2.each_with_index do |part, i|
|
52
|
+
i += cols1
|
53
|
+
parts[i] = (parts[i].nil? or parts[i].empty?) ? part : parts[i] << "|" << part
|
54
|
+
end
|
55
|
+
key2 = nil
|
56
|
+
while key2.nil? and not done2
|
57
|
+
if file2.eof?; done2 = true; else key2, *parts2 = file2.gets.sub("\n",'').split(sep, -1) end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
output.puts [key, parts].flatten * sep
|
62
|
+
parts = [""] * (cols1 + cols2)
|
63
|
+
|
64
|
+
case
|
65
|
+
when done1
|
66
|
+
key = key2
|
67
|
+
when done2
|
68
|
+
key = key1
|
69
|
+
else
|
70
|
+
key = key1 < key2 ? key1 : key2
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
output.close
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.paste(file1, file2, output, sep = "\t")
|
78
|
+
case
|
79
|
+
when (String === file1 and not file1.index("\n") and file1.length < 250 and File.exists?(file1))
|
80
|
+
file1 = CMD.cmd("sort -k1,1 -t'#{sep}' #{ file1 } ", :pipe => true)
|
81
|
+
when String === file1
|
82
|
+
file1 = CMD.cmd("sort -k1,1 -t'#{sep}'", :in => file1, :pipe => true)
|
83
|
+
end
|
84
|
+
|
85
|
+
case
|
86
|
+
when (String === file2 and not file2.index("\n") and file2.length < 250 and File.exists?(file2))
|
87
|
+
file2 = CMD.cmd("sort -k1,1 -t'#{sep}' #{ file2 } ", :pipe => true)
|
88
|
+
when String === file2
|
89
|
+
file2 = CMD.cmd("sort -k1,1 -t'#{sep}'", :in => file2, :pipe => true)
|
90
|
+
end
|
91
|
+
|
92
|
+
output = File.open(output, 'w') if String === output
|
93
|
+
|
94
|
+
cols1 = nil
|
95
|
+
cols2 = nil
|
96
|
+
|
97
|
+
done1 = false
|
98
|
+
done2 = false
|
99
|
+
|
100
|
+
while (line1 = file1.gets) =~ /#/; end
|
101
|
+
line1.strip!
|
102
|
+
parts1 = line1.split(sep)
|
103
|
+
key1 = parts1.shift
|
104
|
+
cols1 = parts1.length
|
2
105
|
|
106
|
+
while (line2 = file2.gets) =~ /#/; end
|
107
|
+
line2.strip!
|
108
|
+
parts2 = line2.split(sep)
|
109
|
+
key2 = parts2.shift
|
110
|
+
cols2 = parts2.length
|
111
|
+
while not (done1 or done2)
|
112
|
+
case
|
113
|
+
when key1 < key2
|
114
|
+
output.puts [key1, parts1, [""] * cols2] * sep
|
115
|
+
if file1.eof?
|
116
|
+
done1 = true
|
117
|
+
else
|
118
|
+
line1 = file1.gets
|
119
|
+
line1.strip!
|
120
|
+
parts1 = line1.split(sep)
|
121
|
+
key1 = parts1.shift
|
122
|
+
end
|
123
|
+
when key2 < key1
|
124
|
+
output.puts [key2, [""] * cols1, parts2] * sep
|
125
|
+
if file2.eof?
|
126
|
+
done2 = true
|
127
|
+
else
|
128
|
+
line2 = file2.gets
|
129
|
+
line2.strip!
|
130
|
+
parts2 = line2.split(sep)
|
131
|
+
key2 = parts2.shift
|
132
|
+
end
|
133
|
+
when key1 == key2
|
134
|
+
output.puts [key1, parts1, parts2] * sep
|
135
|
+
if file1.eof?
|
136
|
+
done1 = true
|
137
|
+
else
|
138
|
+
line1 = file1.gets
|
139
|
+
line1.strip!
|
140
|
+
parts1 = line1.split(sep)
|
141
|
+
key1 = parts1.shift
|
142
|
+
end
|
143
|
+
if file2.eof?
|
144
|
+
done2 = true
|
145
|
+
else
|
146
|
+
line2 = file2.gets
|
147
|
+
line2.strip!
|
148
|
+
parts2 = line2.split(sep)
|
149
|
+
key2 = parts2.shift
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
while not done1
|
155
|
+
output.puts [key1, parts1, [""] * cols2] * sep
|
156
|
+
if file1.eof?
|
157
|
+
done1 = true
|
158
|
+
else
|
159
|
+
line1 = file1.gets
|
160
|
+
line1.strip!
|
161
|
+
parts1 = line1.split(sep)
|
162
|
+
key1 = parts1.shift
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
while not done2
|
167
|
+
output.puts [key2, [""] * cols1, parts2] * sep
|
168
|
+
if file2.eof?
|
169
|
+
done2 = true
|
170
|
+
else
|
171
|
+
line2 = file2.gets
|
172
|
+
line2.strip!
|
173
|
+
parts2 = line2.split(sep)
|
174
|
+
key2 = parts2.shift
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
output.close
|
179
|
+
end
|
3
180
|
#{{{ Attach Methods
|
4
181
|
|
5
182
|
def attach_same_key(other, fields = nil)
|
@@ -26,7 +203,7 @@ class TSV
|
|
26
203
|
def attach_source_key(other, source, fields = nil)
|
27
204
|
fields = other.fields - [key_field].concat(self.fields) if fields.nil?
|
28
205
|
|
29
|
-
other = other.tsv unless TSV === other
|
206
|
+
other = other.tsv(:persistence => :no_create) unless TSV === other
|
30
207
|
field_positions = fields.collect{|field| other.identify_field field}
|
31
208
|
field_names = field_positions.collect{|pos| pos == :key ? other.key_field : other.fields[pos] }
|
32
209
|
|
@@ -87,7 +264,11 @@ class TSV
|
|
87
264
|
next unless other.include? source_key
|
88
265
|
new_values = field_positions.collect do |pos|
|
89
266
|
if pos == :key
|
90
|
-
|
267
|
+
if other.type == :double
|
268
|
+
[source_key]
|
269
|
+
else
|
270
|
+
source_key
|
271
|
+
end
|
91
272
|
else
|
92
273
|
other[source_key][pos]
|
93
274
|
end
|
@@ -119,7 +300,10 @@ class TSV
|
|
119
300
|
#{{{ Attach Helper
|
120
301
|
|
121
302
|
# May make an extra index!
|
122
|
-
def self.find_path(files,
|
303
|
+
def self.find_path(files, options = {})
|
304
|
+
options = Misc.add_defaults options, :in_namespace => false
|
305
|
+
in_namespace = options[:in_namespace]
|
306
|
+
|
123
307
|
if in_namespace
|
124
308
|
ids = [files.first.all_namespace_fields(in_namespace)]
|
125
309
|
ids += files[1..-1].collect{|f| f.all_fields}
|
@@ -128,9 +312,6 @@ class TSV
|
|
128
312
|
end
|
129
313
|
id_list = []
|
130
314
|
|
131
|
-
ids.flatten.each do |field|
|
132
|
-
end
|
133
|
-
|
134
315
|
ids.each_with_index do |list, i|
|
135
316
|
break if i == ids.length - 1
|
136
317
|
match = list.select{|field|
|
@@ -148,8 +329,12 @@ class TSV
|
|
148
329
|
end
|
149
330
|
end
|
150
331
|
|
151
|
-
def self.build_traverse_index(files,
|
152
|
-
|
332
|
+
def self.build_traverse_index(files, options = {})
|
333
|
+
options = Misc.add_defaults options, :in_namespace => false, :persist_input => false
|
334
|
+
in_namespace = options[:in_namespace]
|
335
|
+
persist_input = options[:persist_input]
|
336
|
+
|
337
|
+
path = find_path(files, options)
|
153
338
|
|
154
339
|
return nil if path.nil?
|
155
340
|
|
@@ -157,11 +342,10 @@ class TSV
|
|
157
342
|
|
158
343
|
Log.medium "Found Traversal: #{traversal_ids * " => "}"
|
159
344
|
|
160
|
-
current_key = files.first.all_fields.first
|
161
|
-
target = files.last.all_fields.first
|
162
|
-
target = nil
|
163
345
|
current_id, current_file = path.shift
|
164
|
-
|
346
|
+
current_key = current_file.all_fields.first
|
347
|
+
|
348
|
+
index = current_file.index :target => current_id, :fields => current_key, :persistence => persist_input
|
165
349
|
|
166
350
|
while not path.empty?
|
167
351
|
current_id, current_file = path.shift
|
@@ -175,7 +359,10 @@ class TSV
|
|
175
359
|
index
|
176
360
|
end
|
177
361
|
|
178
|
-
def self.find_traversal(tsv1, tsv2,
|
362
|
+
def self.find_traversal(tsv1, tsv2, options = {})
|
363
|
+
options = Misc.add_defaults options, :in_namespace => false
|
364
|
+
in_namespace = options[:in_namespace]
|
365
|
+
|
179
366
|
identifiers1 = tsv1.identifier_files || []
|
180
367
|
identifiers2 = tsv2.identifier_files || []
|
181
368
|
|
@@ -188,7 +375,7 @@ class TSV
|
|
188
375
|
files1.push identifiers1.shift
|
189
376
|
identifiers2.each_with_index do |e,i|
|
190
377
|
files2 = identifiers2[(0..i)]
|
191
|
-
index = build_traverse_index(files1 + files2.reverse,
|
378
|
+
index = build_traverse_index(files1 + files2.reverse, options)
|
192
379
|
return index if not index.nil?
|
193
380
|
end
|
194
381
|
end
|
@@ -197,8 +384,8 @@ class TSV
|
|
197
384
|
end
|
198
385
|
|
199
386
|
def attach(other, fields = nil, options = {})
|
200
|
-
options
|
201
|
-
in_namespace =
|
387
|
+
options = Misc.add_defaults options, :in_namespace => false
|
388
|
+
in_namespace = options[:in_namespace]
|
202
389
|
|
203
390
|
fields = other.fields - [key_field].concat(self.fields) if fields == :all
|
204
391
|
fields = other.fields_in_namespace - [key_field].concat(self.fields) if fields.nil?
|
@@ -211,7 +398,7 @@ class TSV
|
|
211
398
|
when (in_namespace and self.fields_in_namespace.include?(other.key_field))
|
212
399
|
attach_source_key other, other.key_field, fields
|
213
400
|
else
|
214
|
-
index = TSV.find_traversal(self, other,
|
401
|
+
index = TSV.find_traversal(self, other, options)
|
215
402
|
raise "Cannot traverse identifiers" if index.nil?
|
216
403
|
attach_index other, index, fields
|
217
404
|
end
|
@@ -225,4 +412,20 @@ class TSV
|
|
225
412
|
reorder :key, detached_fields
|
226
413
|
end
|
227
414
|
|
415
|
+
def paste(other, options = {})
|
416
|
+
tmpfile = TmpFile.tmp_file
|
417
|
+
TSV.paste(self.to_s, other.to_s, tmpfile)
|
418
|
+
|
419
|
+
new = TSV.new(tmpfile, options)
|
420
|
+
|
421
|
+
new.key_field = self.key_field unless self.key_field.nil?
|
422
|
+
if self.fields and other.fields
|
423
|
+
new.fields = self.fields + other.fields
|
424
|
+
end
|
425
|
+
|
426
|
+
FileUtils.rm tmpfile if File.exists? tmpfile
|
427
|
+
|
428
|
+
new
|
429
|
+
end
|
430
|
+
|
228
431
|
end
|
data/lib/rbbt/util/tsv/index.rb
CHANGED
@@ -232,6 +232,7 @@ class TSV
|
|
232
232
|
end
|
233
233
|
|
234
234
|
def self.field_matches(tsv, values)
|
235
|
+
values = [values] if not Array === values
|
235
236
|
if values.flatten.sort[0..9].compact.collect{|n| n.to_i} == (1..10).to_a
|
236
237
|
return {}
|
237
238
|
end
|
@@ -247,7 +248,7 @@ class TSV
|
|
247
248
|
if tsv.type == :double
|
248
249
|
tsv.through do |key,entry_values|
|
249
250
|
fields.zip(entry_values).each do |field,entry_field_values|
|
250
|
-
field_values[field].concat entry_field_values
|
251
|
+
field_values[field].concat entry_field_values unless entry_field_values.nil?
|
251
252
|
end
|
252
253
|
end
|
253
254
|
else
|
@@ -271,6 +272,10 @@ class TSV
|
|
271
272
|
TSV.field_matches(self, values)
|
272
273
|
end
|
273
274
|
|
275
|
+
def guess_field(values)
|
276
|
+
field_matches(values).sort_by{|field, matches| matches.uniq.length}.last
|
277
|
+
end
|
278
|
+
|
274
279
|
def sorted_index(pos_start = nil, pos_end = nil)
|
275
280
|
raise "Please specify indexing fields" if (pos_start.nil? and fields.length > 2)
|
276
281
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
class TSV
|
3
2
|
|
4
3
|
def through(new_key_field = :key, new_fields = nil, &block)
|
@@ -177,7 +176,7 @@ class TSV
|
|
177
176
|
new.filename = filename
|
178
177
|
new.case_insensitive = case_insensitive
|
179
178
|
|
180
|
-
|
179
|
+
case
|
181
180
|
when (method.nil? and block_given?)
|
182
181
|
through do |key, values|
|
183
182
|
new[key] = values if yield key, values
|
@@ -199,7 +198,7 @@ class TSV
|
|
199
198
|
method = method.values.first
|
200
199
|
case
|
201
200
|
when (Array === method and (key == :key or key_field == key))
|
202
|
-
method.each{|item|
|
201
|
+
method.each{|item| new[item] = self[item] if self.include? item}
|
203
202
|
when Array === method
|
204
203
|
through :key, key do |key, values|
|
205
204
|
new[key] = self[key] if (values.flatten & method).any?
|
@@ -250,9 +249,9 @@ class TSV
|
|
250
249
|
def add_field(name = nil)
|
251
250
|
each do |key, values|
|
252
251
|
new_values = yield(key, values)
|
253
|
-
new_values = [new_values] if type == :double and not Array
|
252
|
+
new_values = [new_values] if type == :double and not Array === new_values
|
254
253
|
|
255
|
-
self[key] = values + [
|
254
|
+
self[key] = values + [new_values]
|
256
255
|
end
|
257
256
|
|
258
257
|
self.fields = self.fields + [name] if fields != nil and name != nil
|
data/lib/rbbt/util/tsv/parse.rb
CHANGED
@@ -7,6 +7,7 @@ class TSV
|
|
7
7
|
## split with delimiter, do not remove empty
|
8
8
|
fields = io.split(delimiter, -1)
|
9
9
|
|
10
|
+
|
10
11
|
fields
|
11
12
|
end
|
12
13
|
|
@@ -116,6 +117,8 @@ class TSV
|
|
116
117
|
fix, exclude, select, grep =
|
117
118
|
Misc.process_options options, :fix, :exclude, :select, :grep
|
118
119
|
|
120
|
+
exclude ||= Misc.process_options options, :reject if options.include? :reject
|
121
|
+
|
119
122
|
#{{{ Process rest
|
120
123
|
data = {}
|
121
124
|
single = type.to_sym != :double
|
@@ -152,9 +155,10 @@ class TSV
|
|
152
155
|
if single
|
153
156
|
ids = parse_fields(parts[key_pos], sep2)
|
154
157
|
ids.collect!{|id| id.downcase} if case_insensitive
|
158
|
+
ids = ids.reject{|_id| _id.empty?}.uniq
|
155
159
|
|
156
160
|
id = ids.shift
|
157
|
-
ids.each do |id2| data[id2] = "__Ref:#{id}"
|
161
|
+
ids.each do |id2| data[id2] = "__Ref:#{id}" unless data.include? id2 end
|
158
162
|
|
159
163
|
next if data.include?(id) and type != :flat
|
160
164
|
|
@@ -171,8 +175,8 @@ class TSV
|
|
171
175
|
|
172
176
|
extra.collect! do |elem|
|
173
177
|
case
|
174
|
-
when String === cast
|
175
|
-
elem.send(cast)
|
178
|
+
when (String === cast or Symbol === cast)
|
179
|
+
elem.send(cast.to_s)
|
176
180
|
when Proc === cast
|
177
181
|
cast.call elem
|
178
182
|
end
|
@@ -195,9 +199,26 @@ class TSV
|
|
195
199
|
else
|
196
200
|
ids = parse_fields(parts[key_pos], sep2)
|
197
201
|
ids.collect!{|id| id.downcase} if case_insensitive
|
202
|
+
ids = ids.reject{|_id| _id.empty?}.uniq
|
203
|
+
|
204
|
+
next if ids.empty?
|
198
205
|
|
199
206
|
id = ids.shift
|
200
|
-
|
207
|
+
while data.include? id and data[id] =~ /__Ref:(.*)/
|
208
|
+
data[id] = data[$1].collect{|e| e.dup}
|
209
|
+
end
|
210
|
+
|
211
|
+
all_ids = [id]
|
212
|
+
ids.each do |id2|
|
213
|
+
if data.include? id2
|
214
|
+
while data[id2] =~ /__Ref:(.*)/
|
215
|
+
data[id2] = data[$1].collect{|e| e.dup}
|
216
|
+
end
|
217
|
+
all_ids << id2
|
218
|
+
else
|
219
|
+
data[id2] = "__Ref:#{id}"
|
220
|
+
end
|
221
|
+
end
|
201
222
|
|
202
223
|
if other_pos.nil? or (fields == nil and type == :flat)
|
203
224
|
other_pos = (0..(parts.length - 1)).to_a
|
@@ -207,31 +228,34 @@ class TSV
|
|
207
228
|
extra = parts.values_at(*other_pos).collect{|f| parse_fields(f, sep2)}
|
208
229
|
extra.collect! do |list|
|
209
230
|
case
|
210
|
-
when String === cast
|
211
|
-
list.collect{|elem| elem.send(cast)}
|
231
|
+
when (String === cast or Symbol === cast)
|
232
|
+
list.collect{|elem| elem.send(cast.to_s)}
|
212
233
|
when Proc === cast
|
213
234
|
list.collect{|elem| cast.call elem}
|
214
235
|
end
|
215
236
|
end if cast
|
216
237
|
|
217
238
|
max_cols = extra.size if extra.size > (max_cols || 0)
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
data[id] = extra
|
239
|
+
|
240
|
+
all_ids.each do |id|
|
241
|
+
if not merge
|
242
|
+
data[id] = extra unless data.include? id
|
223
243
|
else
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
244
|
+
if not data.include? id
|
245
|
+
data[id] = extra
|
246
|
+
else
|
247
|
+
entry = data[id]
|
248
|
+
while entry =~ /__Ref:(.*)/ do entry = data[$1] end
|
249
|
+
extra.each_with_index do |f, i|
|
250
|
+
if f.empty?
|
251
|
+
next unless keep_empty
|
252
|
+
f= [""]
|
253
|
+
end
|
254
|
+
entry[i] ||= []
|
255
|
+
entry[i] = entry[i].concat f
|
230
256
|
end
|
231
|
-
|
232
|
-
entry[i] = entry[i].concat f
|
257
|
+
data[id] = entry
|
233
258
|
end
|
234
|
-
data[id] = entry
|
235
259
|
end
|
236
260
|
end
|
237
261
|
end
|
@@ -254,7 +278,7 @@ class TSV
|
|
254
278
|
|
255
279
|
fields = nil if Fixnum === fields or (Array === fields and fields.select{|f| Fixnum === f}.any?)
|
256
280
|
fields ||= other_fields
|
257
|
-
[data, {:key_field => key_field, :fields => fields, :type => type, :case_insensitive => case_insensitive, :namespace => namespace, :datadir => options[:datadir], :identifiers => options[:identifiers], :cast =>
|
281
|
+
[data, {:key_field => key_field, :fields => fields, :type => type, :case_insensitive => case_insensitive, :namespace => namespace, :datadir => options[:datadir], :identifiers => options[:identifiers], :cast => (cast.nil? ? false : cast)}]
|
258
282
|
end
|
259
283
|
|
260
284
|
end
|