rbbt-util 5.4.1 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +8 -8
  2. data/bin/rbbt_monitor.rb +8 -4
  3. data/lib/rbbt.rb +4 -11
  4. data/lib/rbbt/annotations.rb +4 -1
  5. data/lib/rbbt/association.rb +218 -157
  6. data/lib/rbbt/association/index.rb +92 -0
  7. data/lib/rbbt/association/item.rb +44 -0
  8. data/lib/rbbt/entity.rb +4 -0
  9. data/lib/rbbt/fix_width_table.rb +14 -9
  10. data/lib/rbbt/knowledge_base.rb +269 -0
  11. data/lib/rbbt/persist.rb +1 -1
  12. data/lib/rbbt/persist/tsv.rb +22 -2
  13. data/lib/rbbt/resource.rb +0 -1
  14. data/lib/rbbt/resource/path.rb +1 -1
  15. data/lib/rbbt/resource/util.rb +0 -1
  16. data/lib/rbbt/tsv.rb +15 -14
  17. data/lib/rbbt/tsv/accessor.rb +21 -16
  18. data/lib/rbbt/tsv/attach.rb +5 -5
  19. data/lib/rbbt/tsv/attach/util.rb +4 -2
  20. data/lib/rbbt/tsv/change_id.rb +67 -0
  21. data/lib/rbbt/tsv/index.rb +5 -3
  22. data/lib/rbbt/tsv/manipulate.rb +83 -37
  23. data/lib/rbbt/tsv/parser.rb +2 -1
  24. data/lib/rbbt/tsv/util.rb +2 -0
  25. data/lib/rbbt/util/cmd.rb +1 -2
  26. data/lib/rbbt/util/log.rb +42 -38
  27. data/lib/rbbt/util/misc.rb +134 -46
  28. data/lib/rbbt/util/open.rb +3 -17
  29. data/lib/rbbt/util/semaphore.rb +8 -2
  30. data/lib/rbbt/workflow.rb +31 -46
  31. data/lib/rbbt/workflow/accessor.rb +1 -1
  32. data/lib/rbbt/workflow/step.rb +5 -3
  33. data/share/rbbt_commands/workflow/server +1 -0
  34. data/share/rbbt_commands/workflow/task +12 -2
  35. data/test/rbbt/association/test_index.rb +36 -0
  36. data/test/rbbt/test_annotations.rb +5 -4
  37. data/test/rbbt/test_association.rb +40 -13
  38. data/test/rbbt/test_knowledge_base.rb +103 -0
  39. data/test/rbbt/test_workflow.rb +4 -2
  40. data/test/rbbt/tsv/test_change_id.rb +43 -0
  41. data/test/rbbt/tsv/test_index.rb +2 -1
  42. data/test/rbbt/tsv/test_manipulate.rb +51 -0
  43. data/test/rbbt/util/test_misc.rb +21 -1
  44. data/test/test_helper.rb +8 -4
  45. metadata +12 -86
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MzQxM2Y1NGU5NjBmNjc1M2IzZWExM2ZiNzU0OWIxNzRiZjYyMjdkNg==
4
+ YmMyOGI4NDhhMTM5MzQxYjE2NjdlZWFhMDFlZjdjZmQxMjdkMjU5Nw==
5
5
  data.tar.gz: !binary |-
6
- MjUwNjI3OTFhODA5YTQ0ZThkYzM5OWNiZjFjZGRmMDU1MWY3ZjI0NQ==
6
+ ZTZkNGIyYzI2OWU4MGJiOWFlYmFmMTcwNDViOWNhNDgwNDdjMjM5Zg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- Y2Y5ZjBmNWVkOTkwZDNiNmI5NTkyMzJkZjZkN2M1OTY1ZmQzZWQyMWY2Zjkz
10
- YTM3MTIyODJkMmRhZjQwNzI3NTIxZjNiNWQzY2Q0ODlmZDg2ODU1OWUxYTkx
11
- YTIyOTRjNzA2MGNhZjNmOGVhYmEwMjJjMzZkYjM0ZDNhM2QwZjc=
9
+ OTBiNzM0NzhiMTk5YzFmMzIzMGU2NDI4YjVkNjM5OWIzZmExZmIyYTllODNj
10
+ MzQwZThkNTg4MzQ2NGQ2OTljZTI4Y2I4MmIxOTdlMjA5M2FlNDQ2ZWU2MjM4
11
+ NThjYTM1OGM4MjAxYTUxZWZlZmM3MGIzNWRiMzlhYjU4ZjA4NDk=
12
12
  data.tar.gz: !binary |-
13
- ZDI1MmRlNzhmZGI5ZDJmZGJiNzMyMTkzNjY1ODUyYjQ2ZTM1ZTZlZTBkOTZk
14
- N2ZlMDAzOGRmOWY3ZmUyZDExZGVlNDVhOTIzMDQzMjhiNTc3MjgzMjA1Y2Rk
15
- YjUxN2MxMjM3NDEyNTk4MGZjYTI2YjlhODZkOTY0YTg0NjFlOTU=
13
+ Y2M1NWYyYzA4MWE2N2U3ZDc3ZjEzMWUzNDQ4ZjAxNjc4Y2FkMTAzOTIyNmY5
14
+ NDZmZDNlNDExYTc2MzBlOWE1NDcxNjU5YWE2ZmMzODU5ODdjZGE1YzNjZTVi
15
+ ZDE4Mjk0YWFkMGEzZDM0MTUzMmI2YjljZWMxNTZlNTE1MGMzZGM=
data/bin/rbbt_monitor.rb CHANGED
@@ -26,8 +26,10 @@ def print_job(file, info, severity_color = nil)
26
26
  else
27
27
  info ||= {:status => :missing_info_file}
28
28
  str = [clean_file, info[:status].to_s] * " [ STATUS = " + " ]"
29
- str += " (#{running?(info)? :running : :dead} #{info[:pid]})" if info[:pid]
30
- str += " (children: #{info[:children_pids].collect{|pid| [pid, Misc.pid_exists?(pid) ? "R" : "D"] * ":"} * ", "})" if info.include? :children_pids
29
+ if info[:status] != :error and info[:status] != :aborted
30
+ str += " (#{running?(info)? :running : :dead} #{info[:pid]})" if info[:pid]
31
+ str += " (children: #{info[:children_pids].collect{|pid| [pid, Misc.pid_exists?(pid) ? "R" : "D"] * ":"} * ", "})" if info.include? :children_pids
32
+ end
31
33
 
32
34
  str = "#{severity_color}" << str << "\033[0m" if severity_color
33
35
  puts str
@@ -52,8 +54,10 @@ def list_jobs(options)
52
54
  color = case
53
55
  when (not info)
54
56
  Log::SEVERITY_COLOR[3]
55
- when info[:status] == :error
57
+ when info[:status] == :error
56
58
  Log::SEVERITY_COLOR[3]
59
+ when info[:status] == :aborted
60
+ Log::SEVERITY_COLOR[2]
57
61
  when (info[:pid] and not running? info)
58
62
  Log::SEVERITY_COLOR[2]
59
63
  end
@@ -93,7 +97,7 @@ def clean_jobs(options)
93
97
  case
94
98
  when options[:all]
95
99
  remove_job file
96
- when (options[:errors] and (not info or info[:status] == :error))
100
+ when (options[:errors] and (not info or info[:status] == :error or info[:status] == :aborted))
97
101
  remove_job file
98
102
  when (options[:zombies] and info[:pid] and not running? info)
99
103
  remove_job file
data/lib/rbbt.rb CHANGED
@@ -1,19 +1,12 @@
1
1
  require 'rbbt/resource'
2
- require 'rbbt/util/open'
3
- require 'rbbt/util/cmd'
4
- require 'rbbt/util/tmpfile'
5
- require 'rbbt/util/filecache'
6
- require 'rbbt/tsv'
7
- require 'rbbt/persist'
8
- require 'rbbt/util/misc'
9
2
 
10
3
  module Rbbt
11
4
  extend Resource
12
5
  pkgdir = 'rbbt'
13
6
  end
14
7
 
15
- Open.cachedir = Rbbt.var.cache["open-remote"].find :user
16
- TmpFile.tmpdir = Rbbt.tmp.find :user
17
- FileCache.cachedir = Rbbt.var.cache.filecache.find :user
18
- Persist.cachedir = Rbbt.var.cache.persistence.find :user
8
+ #Open.cachedir = Rbbt.var.cache["open-remote"].find :user
9
+ #TmpFile.tmpdir = Rbbt.tmp.find :user
10
+ #FileCache.cachedir = Rbbt.var.cache.filecache.find :user
11
+ #Persist.cachedir = Rbbt.var.cache.persistence.find :user
19
12
 
@@ -1,3 +1,4 @@
1
+ require 'rbbt/tsv'
1
2
  require 'rbbt/util/misc'
2
3
  require 'rbbt/annotations/annotated_array'
3
4
  require 'rbbt/annotations/util'
@@ -253,13 +254,15 @@ module Annotation
253
254
  annotation_values = annotation_values.nil? ? {} : annotation_values.dup
254
255
  annotation_values.instance_variable_set(:@annotation_md5, nil)
255
256
 
256
- annotations.zip(values).each do |name, value|
257
+ annotations.each_with_index do |name,i|
258
+ value = values[i]
257
259
 
258
260
  value = value.split("|") if String === value and value.index "|"
259
261
 
260
262
  annotation_values[name] = value
261
263
  end
262
264
 
265
+
263
266
  object.instance_variable_set(:@annotation_values, annotation_values)
264
267
 
265
268
  object.reset
@@ -1,4 +1,6 @@
1
1
  require 'rbbt-util'
2
+ require 'rbbt/tsv/change_id'
3
+ require 'rbbt/association/index'
2
4
 
3
5
  module Association
4
6
  class << self
@@ -8,147 +10,206 @@ module Association
8
10
  end
9
11
  end
10
12
 
11
- def self.register(database, file, options = {})
12
- self.databases[database.to_s] = [file, options]
13
- end
13
+ def self.add_reciprocal(tsv)
14
14
 
15
- def self.get_database(database)
16
- self.databases[database.to_s]
17
- end
15
+ new = {}
16
+ tsv.with_unnamed do
17
+ tsv.through do |key, values|
18
+ new[key] ||= values
19
+ Misc.zip_fields(values).each do |fields|
20
+ target, *rest = fields
21
+
22
+ target_values = new[target] || tsv[target] || [[]] * values.length
23
+ zipped_target_values = Misc.zip_fields(target_values)
24
+
25
+ zipped_target_values << ([key].concat rest)
26
+
27
+ new_values = Misc.zip_fields zipped_target_values
28
+
29
+ new[target] = new_values
30
+ end
31
+ end
32
+ end
33
+
34
+ tsv.annotate(new)
18
35
 
19
- def self.open_database(database, options = {}, persist_options = {})
20
- file, database_options = get_database database
21
- open(file, database_options.merge(options), persist_options)
36
+ new
22
37
  end
23
38
 
24
- def self.index_database(database, options = {}, persist_options = {})
25
- file, database_options = databases[database.to_s]
26
- index(file, database_options.merge(options), persist_options)
39
+ def self.resolve_field(name, fields)
40
+ entity_type = Entity.formats[name]
41
+ return "Field #{ name } could not be resolved: #{fields}" if entity_type.nil?
42
+ field = fields.select{|f| Entity.formats[f] == entity_type}.first
43
+ [field, nil, name]
27
44
  end
28
45
 
29
- def self.parse_field_specification(spec, fields)
46
+ def self.parse_field_specification(spec)
47
+ return [2,nil,nil] if Fixnum === spec
30
48
  spec = spec.split "=>" unless Array === spec
31
- field_part, final_type = spec
49
+ field_part, final_format = spec
32
50
 
33
- field, type = field_part.split "=~"
51
+ field, format = field_part.split "=~"
34
52
 
35
- [field, type, final_type]
53
+ [field, format, final_format]
36
54
  end
37
55
 
38
- def self.resolve_field(name, fields)
39
- type = Entity.formats[name]
40
- return "Field #{ name } could not be resolved: #{fields}" if type.nil?
41
- field = fields.select{|f| Entity.formats[f] == type}.first
42
- [field, nil, name]
43
- end
56
+ def self.calculate_headers(key_field, fields, spec)
57
+ all_fields = [key_field].concat fields if fields and key_field
58
+ field, header, format = parse_field_specification spec if spec
44
59
 
45
- def self.add_reciprocal(tsv)
46
- new_tsv = {}
47
- tsv.with_unnamed do
60
+ if field and key_field == field and not all_fields.include? field
61
+ field, header, format = resolve_field field, all_fields
62
+ end
48
63
 
49
- tsv.through do |target,v|
50
- source_values = tsv.type == :double ? Misc.zip_fields(v) : [v]
51
-
52
- source_values.each do |values|
53
- source = values.shift
54
- values.unshift target
55
- current = new_tsv[source] || tsv[source]
56
-
57
- case tsv.type
58
- when :double
59
- new = current ? current.zip(values).collect{|p| p.flatten} : values.collect{|p| [p]}
60
- when :flat
61
- new = current ? (current + values).compact.uniq : values
64
+ [field, header, format]
65
+ end
66
+
67
+ #{{{ Open
68
+
69
+ def self.open_tsv(file, source, source_header, target, target_header, all_fields, options)
70
+ fields = all_fields.dup
71
+ fields.delete source
72
+ fields.delete target
73
+ fields.unshift target
74
+
75
+ open_options = options.merge({
76
+ :persist => false,
77
+ :key_field => all_fields.index(source),
78
+ :fields => fields.collect{|f| String === f ? all_fields.index(f): f },
79
+ :type => options[:type].to_s == :flat ? :flat : :double,
80
+ :merge => options[:type].to_s == :flat ? false : true
81
+ })
82
+
83
+ # Preserve first line, which would have been considered a header otherwise
84
+ open_options["header_hash"] = "#" if options["header_hash"] == ""
85
+
86
+ field_headers = all_fields.values_at *open_options[:fields]
87
+
88
+ tsv = case file
89
+ when TSV
90
+ file.fields == field_headers ?
91
+ file :
92
+ file.reorder(source, field_headers)
93
+ else
94
+ TSV.open(file, open_options)
62
95
  end
63
96
 
64
- new_tsv[source] = new
65
- end
66
- end
97
+ tsv.fields = field_headers
98
+ tsv.key_field = source
67
99
 
68
- tsv.merge! new_tsv
100
+ # Fix source header
101
+ if source_header and tsv.key_field != source_header
102
+ tsv.key_field = source_header
103
+ end
104
+
105
+ # Fix target header
106
+ if target_header and tsv.fields.first != target_header
107
+ tsv.fields = tsv.fields.collect{|f| f == target ? target_header : f }
69
108
  end
70
109
 
71
110
  tsv
72
111
  end
73
112
 
74
- def self.load_tsv(file, options)
75
- key_field = TSV.parse_header(file, options).key_field
76
- fields = TSV.parse_header(file, options).fields
77
- all_fields = TSV.parse_header(file, options).all_fields
78
-
79
- source = options[:source] || options[:source_type]
80
- source = TSV.identify_field key_field, fields, options[:key_field] if source.nil? and options[:key_field]
81
- source = all_fields[source] if Fixnum === source
82
- source = key_field if source == :key or source.nil?
83
-
84
- target = options[:target]
85
- target = TSV.identify_field key_field, fields, options[:fields].first if target.nil? and options[:fields]
86
- target = all_fields[target] if Fixnum === target
87
- target = key_field if target == :key
113
+ def self.translate_tsv(tsv, source_final_format, target_final_format)
114
+ source_field = tsv.key_field
115
+ target_field = tsv.fields.first
88
116
 
89
- zipped = options[:zipped]
90
- undirected = options[:undirected]
117
+ if source_final_format and source_field != source_final_format and
118
+ Entity.formats[source_field] and
119
+ Entity.formats[source_field] == Entity.formats[source_final_format]
91
120
 
92
- source, source_header, source_final_type = parse_field_specification source, all_fields
93
- target, target_header, target_final_type = parse_field_specification target, all_fields if target
121
+ Log.debug("Changing source format from #{tsv.key_field} to #{source_final_format}")
94
122
 
95
- if source and not all_fields.include? source
96
- Log.debug("Resolving source: #{ source }")
97
- source, source_header, source_final_type = resolve_field source, all_fields
98
- Log.debug([source, source_header, source_final_type] * ", ")
123
+ tsv.with_unnamed do
124
+ tsv = tsv.change_key source_final_format, :identifiers => Organism.identifiers(tsv.namespace), :persist => true
125
+ end
99
126
  end
100
127
 
101
- if target and not all_fields.include? target
102
- Log.debug("Resolving target: #{ target }")
103
- target, target_header, target_final_type = resolve_field target, all_fields
104
- Log.debug([target, target_header, target_final_type] * ", ")
128
+ # Translate target
129
+ if target_final_format and target_field != target_final_format and
130
+ Entity.formats[target_field] and
131
+ Entity.formats[target_field] == Entity.formats[target_final_format]
132
+
133
+ Log.debug("Changing target format from #{tsv.fields.first} to #{target_final_format}")
134
+
135
+ save_key_field = tsv.key_field
136
+ tsv.key_field = "MASKED"
137
+
138
+ tsv.with_unnamed do
139
+ tsv = tsv.swap_id tsv.fields.first, target_final_format, :identifiers => Organism.identifiers(tsv.namespace), :persist => true
140
+ end
141
+
142
+ tsv.key_field = save_key_field
105
143
  end
144
+ tsv
145
+ end
106
146
 
107
- source_final_type ||= options[:source_type] if options[:source_type]
108
- target_final_type ||= options[:target_type] if options[:target_type]
147
+ def self.specs(all_fields, options = {})
148
+ source_spec, source_format, target_spec, target_format, format, key_field, fields = Misc.process_options options, :source, :source_format, :target, :target_format, :format, :key_field, :fields
109
149
 
110
- Log.debug("Loading associations from: #{ file }")
111
- Log.debug("sources: #{[source, source_header, source_final_type] * ", "}")
112
- Log.debug("targets: #{[target, target_header, target_final_type] * ", "}")
113
- if source != all_fields.first or (target and target != all_fields[1])
114
- fields = ([target] + (all_fields - [source, target])).compact
115
- open_options = options.merge({:key_field => source, :fields => fields})
116
- tsv = TSV.open(file, open_options)
150
+ if key_field and all_fields
151
+ key_pos = (Fixnum === key_field ? key_field : all_fields.index(key_field) )
152
+ key_field = all_fields[key_pos]
117
153
  else
118
- tsv = TSV.open(file, options)
154
+ key_field = all_fields.first if all_fields
119
155
  end
120
156
 
121
- if source_header and tsv.key_field != source_header
122
- tsv.key_field = source_header
157
+ if fields and all_fields
158
+ field_pos = fields.collect{|f| Fixnum === f ? f : fields.index(f) }
159
+ fields = all_fields.values_at *field_pos
160
+ else
161
+ fields = all_fields[1..-1] if all_fields
123
162
  end
124
163
 
125
- if source_final_type and tsv.key_field != source_final_type
126
- Log.debug("Changing source type from #{tsv.key_field} to #{source_final_type}")
127
- tsv.with_unnamed do
128
- tsv = TSVWorkflow.job(:change_id, tsv.filename, :tsv => tsv, :format => source_final_type, :organism => tsv.namespace).exec
129
- end
130
- end
164
+ source, source_header, orig_source_format = calculate_headers(key_field, fields, source_spec)
165
+ source_format ||= orig_source_format
166
+ source = key_field if source.nil?
167
+ source = key_field if source == :key
168
+ source_header ||= source
131
169
 
132
- if target_header and tsv.fields.first != target_header
133
- tsv.fields = tsv.fields.collect{|f| f == target ? target_header : f }
170
+ target, target_header, orig_target_format = calculate_headers(key_field, fields, target_spec)
171
+ target_format ||= orig_target_format
172
+ target = (([key_field] + fields) - [source]).first if target.nil?
173
+ target = key_field if target == :key
174
+ target_header ||= target
175
+
176
+ case format
177
+ when String
178
+ source_format ||= format if Entity.formats[source_header] == Entity.formats[format]
179
+ target_format ||= format if Entity.formats[target_header] == Entity.formats[format]
180
+ when Hash
181
+ _type = Entity.formats[source_header].to_s
182
+ source_format ||= format[_type] if format.include? _type
183
+ _type = Entity.formats[target_header].to_s
184
+ target_format ||= format[_type] if format.include? _type
134
185
  end
135
186
 
136
- if target_final_type and tsv.fields.first != target_final_type and
137
- Entity.formats[tsv.fields.first] and
138
- Entity.formats[tsv.fields.first] == Entity.formats[target_final_type]
187
+ [source, source_header, source_format, target, target_header, target_format]
188
+ end
139
189
 
140
- Log.debug("Changing target type from #{tsv.fields.first} to #{source_final_type}")
141
- save_key_field = tsv.key_field
142
- tsv.key_field = "MASKED"
143
- tsv.with_unnamed do
144
- tsv = TSVWorkflow.job(:swap_id, tsv.filename, :tsv => tsv, :field => tsv.fields.first, :format => target_final_type, :organism => tsv.namespace).exec
145
- end
146
- tsv.key_field = save_key_field
190
+ def self.load_tsv(file, options)
191
+ undirected = Misc.process_options options, :undirected
192
+
193
+ case file
194
+ when Proc
195
+ return load_tsv(file.call, options)
196
+ when TSV
197
+ key_field, *fields = all_fields = file.all_fields
198
+ else
199
+ key_field, *fields = all_fields = TSV.parse_header(file, options.merge(:fields => nil, :key_field => nil)).all_fields
147
200
  end
148
201
 
149
- if undirected
150
- tsv = add_reciprocal tsv
151
- end
202
+ source, source_header, source_format, target, target_header, target_format = specs(all_fields, options)
203
+
204
+ Log.info("Loading associations from: #{ Misc.fingerprint file }")
205
+ Log.info("sources: #{ [source, source_header, source_format].join(", ") }")
206
+ Log.info("targets: #{ [target, target_header, target_format].join(", ") }")
207
+
208
+ tsv = open_tsv(file, source, source_header, target, target_header, all_fields, options)
209
+
210
+ tsv = translate_tsv(tsv, source_format, target_format)
211
+
212
+ tsv = add_reciprocal(tsv) if undirected
152
213
 
153
214
  tsv
154
215
  end
@@ -175,82 +236,82 @@ module Association
175
236
  end
176
237
  end
177
238
 
239
+ #{{{ Index
240
+
241
+ def self.get_index(index_file, write = false)
242
+ Persist.open_tokyocabinet(index_file, write, :list, TokyoCabinet::BDB).tap{|r| r.unnamed = true; Association::Index.setup r }
243
+ end
244
+
178
245
  def self.index(file, options = {}, persist_options = {})
179
246
  options = {} if options.nil?
247
+ options = Misc.add_defaults options, :persist => true
180
248
  persist_options = {} if persist_options.nil?
181
249
 
182
250
  Persist.persist_tsv(file, nil, options, {:persist => true, :prefix => "Association Index"}.merge(persist_options).merge(:engine => TokyoCabinet::BDB, :serializer => :clean)) do |assocs|
183
251
  undirected = options[:undirected]
184
- tsv = TSV === file ? file : Association.open(file, options, persist_options.merge(:persist => false))
252
+ if file
253
+ tsv = TSV === file ? file : Association.open(file, options, persist_options.merge(:persist => false))
185
254
 
186
- key_field = [tsv.key_field, tsv.fields.first.split(":").last, undirected ? "undirected" : nil].compact * "~"
255
+ fields = tsv.fields
256
+ key_field = [tsv.key_field, fields.first.split(":").last, undirected ? "undirected" : nil].compact * "~"
187
257
 
188
- TSV.setup(assocs, :key_field => key_field, :fields => tsv.fields[1..-1], :type => :list, :serializer => :list)
258
+ TSV.setup(assocs, :key_field => key_field, :fields => fields[1..-1], :type => :list, :serializer => :list)
259
+
260
+ tsv.with_unnamed do
261
+ tsv.with_monitor :desc => "Extracting associations" do
262
+ case tsv.type
263
+ when :list
264
+ tsv.through do |source, values|
265
+ target, *rest = values
266
+ next if source.nil? or source.empty? or target.nil? or target.empty?
189
267
 
190
- tsv.with_unnamed do
191
- tsv.with_monitor :desc => "Extracting annotations" do
192
- case tsv.type
193
- when :flat
194
- tsv.through do |source, targets|
195
- next if source.nil? or source.empty? or targets.nil? or targets.empty?
196
-
197
- targets.each do |target|
198
- next if target.nil? or target.empty?
199
268
  key = [source, target] * "~"
200
- assocs[key] = nil
269
+ assocs[key] = rest
270
+ end
271
+ when :flat
272
+ tsv.through do |source, targets|
273
+ next if source.nil? or source.empty? or targets.nil? or targets.empty?
274
+
275
+ targets.each do |target|
276
+ next if target.nil? or target.empty?
277
+ key = [source, target] * "~"
278
+ assocs[key] = nil
279
+ end
201
280
  end
202
- end
203
-
204
- when :double
205
- tsv.through do |source, values|
206
- next if values.empty?
207
- next if source.nil?
208
- next if values.empty?
209
- targets = values.first
210
- rest = Misc.zip_fields values[1..-1]
211
-
212
- annotations = rest.length > 1 ?
213
- targets.zip(rest) :
214
- targets.zip(rest * targets.length)
215
281
 
216
- annotations.each do |target, info|
217
- next if target.nil?
218
- key = [source, target] * "~"
219
- assocs[key] = info
282
+ when :double
283
+ tsv.through do |source, values|
284
+ next if values.empty?
285
+ next if source.nil?
286
+ next if values.empty?
287
+ targets = values.first
288
+ rest = Misc.zip_fields values[1..-1]
289
+
290
+ annotations = rest.length > 1 ?
291
+ targets.zip(rest) :
292
+ targets.zip(rest * targets.length)
293
+
294
+ annotations.each do |target, info|
295
+ next if target.nil?
296
+ key = [source, target] * "~"
297
+ assocs[key] = info
298
+ end
220
299
  end
300
+ else
301
+ raise "Type not supported: #{tsv.type}"
221
302
  end
222
- else
223
- raise "Type not supported: #{tsv.type}"
224
303
  end
225
304
  end
305
+ else
306
+ key_field, fields = options.values_at :key_field, :fields
307
+ TSV.setup(assocs, :key_field => key_field, :fields => fields[1..-1], :type => :list, :serializer => :list)
226
308
  end
227
309
  assocs.close
228
310
 
229
311
  assocs
312
+ end.tap do |assocs|
313
+ Association::Index.setup assocs
230
314
  end
231
315
  end
232
-
233
- def self.connections(repo, entities)
234
- source_field, target_field, undirected = repo.key_field.split("~")
235
-
236
- source_type = Entity.formats[source_field].to_s
237
- target_type = Entity.formats[target_field].to_s
238
-
239
- source_entities = entities[source_type] || entities[source_field]
240
- target_entities = entities[target_type] || entities[target_field]
241
-
242
- return [] if source_entities.nil? or target_entities.nil?
243
-
244
- source_entities.collect do |entity|
245
- keys = repo.prefix(entity + "~")
246
- keys.collect do |key|
247
- source, target = key.split("~")
248
- next unless target_entities.include? target
249
- next if undirected and target > source
250
- info = Hash[*repo.fields.zip(repo[key]).flatten]
251
-
252
- {:source => source, :target => target, :info => info}
253
- end.compact
254
- end.flatten
255
- end
256
316
  end
317
+