scout-gear 10.7.2 → 10.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +46 -32
  3. data/VERSION +1 -1
  4. data/lib/scout/association/index.rb +4 -0
  5. data/lib/scout/association/item.rb +1 -1
  6. data/lib/scout/association.rb +28 -9
  7. data/lib/scout/entity/identifiers.rb +2 -2
  8. data/lib/scout/entity/property.rb +1 -0
  9. data/lib/scout/knowledge_base/enrichment.rb +9 -0
  10. data/lib/scout/knowledge_base/entity.rb +143 -0
  11. data/lib/scout/knowledge_base/list.rb +95 -0
  12. data/lib/scout/knowledge_base/query.rb +96 -0
  13. data/lib/scout/knowledge_base/registry.rb +173 -0
  14. data/lib/scout/knowledge_base/traverse.rb +329 -0
  15. data/lib/scout/knowledge_base.rb +91 -0
  16. data/lib/scout/tsv/annotation.rb +4 -4
  17. data/lib/scout/tsv/index.rb +0 -2
  18. data/lib/scout/tsv/parser.rb +1 -1
  19. data/lib/scout/tsv/stream.rb +3 -3
  20. data/lib/scout/tsv.rb +2 -0
  21. data/lib/scout/workflow/step/info.rb +10 -1
  22. data/scout-gear.gemspec +24 -6
  23. data/scout_commands/kb/config +33 -0
  24. data/scout_commands/kb/entities +35 -0
  25. data/scout_commands/kb/list +39 -0
  26. data/scout_commands/{db → kb}/query +6 -11
  27. data/scout_commands/{db → kb}/register +9 -8
  28. data/scout_commands/{db → kb}/show +6 -16
  29. data/scout_commands/kb/traverse +66 -0
  30. data/test/data/person/brothers +1 -1
  31. data/test/scout/entity/test_identifiers.rb +3 -3
  32. data/test/scout/knowledge_base/test_enrichment.rb +0 -0
  33. data/test/scout/knowledge_base/test_entity.rb +38 -0
  34. data/test/scout/knowledge_base/test_list.rb +40 -0
  35. data/test/scout/knowledge_base/test_query.rb +39 -0
  36. data/test/scout/knowledge_base/test_registry.rb +16 -0
  37. data/test/scout/knowledge_base/test_traverse.rb +245 -0
  38. data/test/scout/test_association.rb +17 -3
  39. data/test/scout/test_entity.rb +0 -15
  40. data/test/scout/test_knowledge_base.rb +27 -0
  41. data/test/test_helper.rb +17 -0
  42. metadata +23 -5
@@ -0,0 +1,173 @@
1
+ require 'scout/association'
2
+ require 'scout/association/item'
3
+
4
+ class KnowledgeBase
5
+ def register(name, file = nil, options = {}, &block)
6
+ file = file.find if Path === file
7
+ @registry ||= IndiferentHash.setup({})
8
+ if block_given?
9
+ block.define_singleton_method(:filename) do name.to_s end
10
+ Log.debug("Registering #{ name } from code block")
11
+ @registry[name] = [block, options]
12
+ else
13
+ Log.debug("Registering #{ name }: #{ Log.fingerprint file } #{Log.fingerprint options}")
14
+ @registry[name] = [file, options]
15
+ end
16
+ end
17
+
18
+ def all_databases
19
+ return [] unless @registry
20
+ @registry.keys
21
+ end
22
+
23
+ def include?(name)
24
+ all_databases.include? name
25
+ end
26
+
27
+ def fields(name)
28
+ @fields ||= {}
29
+ @fields[name] ||= get_index(name).fields
30
+ end
31
+
32
+ def description(name)
33
+ @descriptions ||= {}
34
+ @descriptions[name] ||= get_index(name).key_field.split("~")
35
+ end
36
+
37
+ def source(name)
38
+ description(name)[0]
39
+ end
40
+
41
+ def target(name)
42
+ description(name)[1]
43
+ end
44
+
45
+ def undirected(name)
46
+ description(name)[2]
47
+ end
48
+
49
+ def get_index(name, options = {})
50
+ name = name.to_s
51
+ options[:namespace] ||= self.namespace unless self.namespace.nil?
52
+ @indices ||= IndiferentHash.setup({})
53
+ @indices[[name, options]] ||=
54
+ begin
55
+ if options.empty?
56
+ key = name.to_s
57
+ elsif options[:key]
58
+ key = options[:key]
59
+ key = name if key == :name
60
+ else
61
+ fp = Misc.digest(options)
62
+ key = name.to_s + "_" + fp
63
+ end
64
+
65
+ Persist.memory("Index:" << [key, dir] * "@") do
66
+ options = options.dup
67
+
68
+ persist_dir = dir
69
+ persist_path = persist_dir[key].find
70
+ file, registered_options = registry[name]
71
+
72
+ options = IndiferentHash.add_defaults options, registered_options if registered_options and registered_options.any?
73
+ options = IndiferentHash.add_defaults options, :persist_path => persist_path, :persist_dir => persist_dir, :persist => true
74
+
75
+ if entity_options
76
+ options[:entity_options] ||= {}
77
+ entity_options.each do |type, info|
78
+ options[:entity_options][type] ||= {}
79
+ options[:entity_options][type] = IndiferentHash.add_defaults options[:entity_options][type], info
80
+ end
81
+ end
82
+
83
+ persist_options = IndiferentHash.pull_keys options, :persist
84
+ persist_options = IndiferentHash.add_defaults persist_options
85
+
86
+ index = if persist_path.exists? and persist_options[:persist] and not persist_options[:update]
87
+ Log.low "Re-opening index #{ name } from #{ Log.fingerprint persist_path }. #{options}"
88
+ Association.index(file, **options, persist_options: persist_options.dup)
89
+ else
90
+ options = IndiferentHash.add_defaults options, registered_options if registered_options
91
+ raise "Repo #{ name } not found and not registered" if file.nil?
92
+ Log.medium "Opening index #{ name } from #{ Log.fingerprint file }. #{options}"
93
+ file = file.call if Proc === file
94
+ Association.index(file, **options, persist_options: persist_options.dup)
95
+ end
96
+
97
+ index.namespace = self.namespace unless self.namespace
98
+
99
+ index
100
+ end
101
+ end
102
+ end
103
+
104
+ def get_database(name, options = {})
105
+ options = options.dup
106
+ if self.namespace == options[:namespace]
107
+ options.delete(:namespace)
108
+ end
109
+ @databases ||= IndiferentHash.setup({})
110
+ @databases[[name, options]] ||=
111
+ begin
112
+ fp = Log.fingerprint([name,options])
113
+
114
+ if options.empty?
115
+ key = name.to_s
116
+ else
117
+ fp = Misc.digest(options)
118
+ key = name.to_s + "_" + fp
119
+ end
120
+
121
+ options[:namespace] ||= self.namespace unless self.namespace.nil?
122
+
123
+ key += '.database'
124
+ Persist.memory("Database:" << [key, dir] * "@") do
125
+ options = options.dup
126
+
127
+ persist_dir = dir
128
+ persist_path = persist_dir[key].find
129
+ file, registered_options = registry[name]
130
+
131
+ options = IndiferentHash.add_defaults options, registered_options if registered_options and registered_options.any?
132
+ options = IndiferentHash.add_defaults options, :persist_path => persist_path, :persist => true
133
+
134
+ if entity_options
135
+ options[:entity_options] ||= {}
136
+ entity_options.each do |type, info|
137
+ options[:entity_options][type] ||= {}
138
+ options[:entity_options][type] = IndiferentHash.add_defaults options[:entity_options][type], info
139
+ end
140
+ end
141
+
142
+ persist_options = IndiferentHash.pull_keys options, :persist
143
+
144
+ database = if persist_path.exists? and persist_options[:persist] and not persist_options[:update]
145
+ Log.low "Re-opening database #{ name } from #{ Log.fingerprint persist_path }. #{options}"
146
+ #Association.database(file, **options, persist_options: persist_options)
147
+ Association.database(file, **options.merge(persist_options: persist_options))
148
+ else
149
+ options = IndiferentHash.add_defaults options, registered_options if registered_options
150
+ undirected = IndiferentHash.process_options options, :undirected
151
+ raise "Repo #{ name } not found and not registered" if file.nil?
152
+ Log.medium "Opening database #{ name } from #{ Log.fingerprint file }. #{options}"
153
+ file = file.call if Proc === file
154
+ #Association.database(file, **options, persist_options: persist_options)
155
+ Association.database(file, **options.merge(persist_options: persist_options))
156
+ end
157
+
158
+ database.namespace = self.namespace if self.namespace
159
+
160
+ database
161
+ end
162
+ end
163
+ end
164
+
165
+ def index_fields(name)
166
+ get_index(name).fields
167
+ end
168
+
169
+ def produce(name, *rest,&block)
170
+ register(name, *rest, &block)
171
+ get_index(name)
172
+ end
173
+ end
@@ -0,0 +1,329 @@
1
+ class KnowledgeBase
2
+
3
+ class Traverser
4
+ attr_accessor :rules, :assignments, :matches, :kb
5
+
6
+ def initialize(kb, rules = [])
7
+ @kb = kb
8
+ @rules = rules
9
+ @assignments = {}
10
+ @matches = {}
11
+ end
12
+
13
+ def wildcard(name)
14
+ return name unless is_wildcard?(name)
15
+ assignments[name] || name
16
+ end
17
+
18
+ def is_wildcard?(name)
19
+ name[0] == '?'
20
+ end
21
+
22
+ def is_list?(name)
23
+ name[0] == ':'
24
+ end
25
+
26
+ def identify(db, source, target)
27
+ source_entities = if is_wildcard? source
28
+ assignments[source] || :all
29
+ elsif is_list? source
30
+ kb.load_list(source[1..-1])
31
+ else
32
+ kb.identify_source db, source
33
+ end
34
+
35
+ target_entities = if is_wildcard? target
36
+ assignments[target] || :all
37
+ elsif is_list? target
38
+ kb.load_list(target[1..-1])
39
+ else
40
+ kb.identify_target db, target
41
+ end
42
+
43
+ source_entities = [source_entities] unless Array === source_entities or source_entities == :all
44
+ target_entities = [target_entities] unless Array === target_entities or target_entities == :all
45
+
46
+ [source_entities, target_entities]
47
+ end
48
+
49
+ def reassign(matches, source, target)
50
+ #assignments[source] = (matches.any? ? matches.collect{|m| m.source_entity }.uniq : nil) if is_wildcard? source
51
+ #assignments[target] = (matches.any? ? matches.collect{|m| m.target_entity }.uniq : nil) if is_wildcard? target
52
+ assignments[source] = (matches.any? ? matches.source_entity.uniq : nil) if is_wildcard? source
53
+ assignments[target] = (matches.any? ? matches.target_entity.uniq : nil) if is_wildcard? target
54
+ end
55
+
56
+ def clean_matches(rules, all_matches, assignments)
57
+ paths = {}
58
+
59
+ rules.zip(all_matches).each do |rule, matches|
60
+ source, db, target = rule.split /\s+/
61
+ next if matches.nil?
62
+
63
+ if is_wildcard? source
64
+ assigned = assignments[source] || []
65
+ matches = matches.select{|m| assigned.include? m.partition("~").first }
66
+ end
67
+
68
+ if is_wildcard? target
69
+ assigned = assignments[target] || []
70
+ matches = matches.select{|m| assigned.include? m.partition("~").last }
71
+ end
72
+
73
+ paths[rule] = matches
74
+ end
75
+
76
+ paths
77
+ end
78
+
79
+ def _fp(rules, clean_matches, assignments)
80
+ return true if rules.empty?
81
+
82
+ rule, *rest = rules
83
+ source, db, target = rule.split /\s+/
84
+
85
+ wildcard_source = is_wildcard? source
86
+ wildcard_target = is_wildcard? target
87
+
88
+ paths = {}
89
+ matches = clean_matches[rule]
90
+ matches.each do |match|
91
+ new_assignments = nil
92
+ match_source, _sep, match_target = match.partition "~"
93
+
94
+ if wildcard_source
95
+ next if assignments[source] and assignments[source] != match_source
96
+ new_assignments ||= assignments.dup
97
+ new_assignments[source] = match_source
98
+ end
99
+
100
+ if wildcard_target
101
+ next if assignments[target] and assignments[target] != match_target
102
+ new_assignments ||= assignments.dup
103
+ new_assignments[target] = match_target
104
+ end
105
+
106
+ new_paths = _fp(rest, clean_matches, new_assignments)
107
+ next unless new_paths
108
+ paths[match] = new_paths
109
+ end
110
+
111
+ return false if paths.empty?
112
+
113
+ paths
114
+ end
115
+
116
+ def _ep(paths)
117
+ found = []
118
+ paths.each do |match,_next|
119
+ case _next
120
+ when TrueClass
121
+ found << [match]
122
+ when FalseClass
123
+ next
124
+ else
125
+ _ep(_next).each do |_n|
126
+ found << [match] + _n
127
+ end
128
+ end
129
+ end
130
+ found
131
+ end
132
+
133
+ def find_paths(rules, all_matches, assignments)
134
+ clean_matches = clean_matches(rules, all_matches, assignments)
135
+
136
+ path_hash = _fp(rules, clean_matches, {})
137
+
138
+ return [] unless path_hash
139
+ _ep(path_hash).collect do |path|
140
+ path.zip(clean_matches.values_at(*rules)).collect do |item, matches|
141
+ matches.select{|m| m == item }.first
142
+ end
143
+ end
144
+ end
145
+
146
+ def traverse_db(db, source, target, conditions)
147
+ source_entities, target_entities = identify db, source, target
148
+
149
+ options = {:source => source_entities, :target => target_entities}
150
+ Log.debug "Traversing #{ db }: #{Log.fingerprint options}"
151
+ matches = kb.subset(db, options)
152
+
153
+ if conditions
154
+ Misc.tokenize(conditions).each do |condition|
155
+ if condition.index "="
156
+ key, value = condition.split("=")
157
+ matches = matches.select{|m| Misc.match_value(m.info[key.strip], value)}
158
+ else
159
+ matches = matches.select{|m| m.info[condition.strip].to_s =~ /\btrue\b/}
160
+ end
161
+ end
162
+ end
163
+
164
+ matches
165
+ end
166
+
167
+ def id_dbs(db)
168
+ # ToDo: Revise this, I'm not sure what id does anymore
169
+ # I think it deals with syndication
170
+ if db.include? '?'
171
+ all_dbs = kb.registry.keys.collect{|k| k.to_s }
172
+ _name, _sep, _kb = db.partition("@")
173
+ case
174
+ when _name[0] == '?'
175
+ dbs = all_dbs.select{|_db|
176
+ n,_s,d=_db.partition("@");
177
+ d.nil? or d.empty? or (d == _kb and assignments[_name].include?(n))
178
+ }
179
+ when _kb[0] == '?'
180
+ dbs = all_dbs.select{|_db| n,_s,d=_db.partition("@"); n == _name and assignments[_kb].include?(d) }
181
+ end
182
+ else
183
+ dbs = [db]
184
+ end
185
+
186
+ dbs
187
+ end
188
+
189
+ def traverse(nopaths = false)
190
+ all_matches = []
191
+ path_rules = []
192
+ acc_var = nil
193
+ pre_acc_var_assignments = nil
194
+ rules.each do |rule|
195
+ rule = rule.strip
196
+ next if rule.empty?
197
+
198
+ if m = rule.match(/([^\s]+)\s+([^\s=]+)\s+([^\s]+)(?:\s+-\s+(.*))?/)
199
+ Log.debug "Traverse rule: #{rule}"
200
+ path_rules << rule
201
+
202
+ source, db, target, conditions = m.captures
203
+
204
+ dbs = id_dbs(db)
205
+
206
+ rule_matches = nil
207
+ dbs.each do |_db|
208
+ matches = traverse_db(_db, source, target, conditions)
209
+
210
+ next if matches.nil? or matches.empty?
211
+
212
+ # ToDo: Revise this, I'm not sure what id does anymore
213
+ #
214
+ #if db.include? '?'
215
+ # _name, _sep, _kb = db.partition("@")
216
+ # case
217
+ # when _kb[0] == '?'
218
+ # assignments[_kb] ||= []
219
+ # assignments[_kb] << _db.partition("@").reject{|p| p.empty?}.last
220
+ # when _name[0] == '?'
221
+ # assignments[_name] ||= []
222
+ # assignments[_name] << _db.partition("@").first
223
+ # end
224
+ #end
225
+
226
+ if rule_matches.nil?
227
+ rule_matches = matches
228
+ else
229
+ matches.each do |m|
230
+ rule_matches << m
231
+ end
232
+ end
233
+
234
+ assignments.each{|k,v| v.uniq! if v}
235
+ end
236
+
237
+ reassign rule_matches, source, target if rule_matches
238
+
239
+ all_matches << rule_matches
240
+
241
+ elsif m = rule.match(/([^\s=]+)\s*=([^\s]*)\s*(.*)/)
242
+ Log.debug "Assign rule: #{rule}"
243
+ var, db, value_str = m.captures
244
+ names = value_str.split(",").collect{|v| v.strip}
245
+ if db.empty?
246
+ ids = names
247
+ else
248
+ dbs = id_dbs(db)
249
+ names = names.collect{|name| assignments.include?(name) ? assignments[name] : name}.flatten
250
+ ids = names.collect{|name|
251
+ id = nil
252
+ dbs.each do |db|
253
+ sid, tid = identify db, name, name
254
+ id = (sid + tid).compact.first
255
+ break if id
256
+ end
257
+ id
258
+ }
259
+ end
260
+ assignments[var] = ids
261
+
262
+ elsif m = rule.match(/(\?[^\s{]+)\s*{/)
263
+ acc_var = m.captures.first
264
+ pre_acc_var_assignments = assignments.dup
265
+ Log.debug "Start assign block: #{acc_var}"
266
+ elsif m = rule.match(/^\s*}\s*$/)
267
+ Log.debug "Close assign block: #{acc_var}"
268
+ saved_assign = assignments[acc_var]
269
+ assignments.clear
270
+ assignments.merge!(pre_acc_var_assignments)
271
+ pre_acc_var_assignments = nil
272
+ assignments[acc_var] = saved_assign
273
+ all_matches = []
274
+ path_rules = []
275
+ else
276
+ raise "Rule not understood: #{rule}"
277
+ end
278
+ end
279
+
280
+ return [assignments, nil] if nopaths
281
+
282
+ Log.debug "Finding paths: #{all_matches.length}"
283
+ paths = find_paths path_rules, all_matches, assignments
284
+ Log.debug "Found paths: #{paths.length}"
285
+
286
+ [assignments, paths]
287
+ end
288
+
289
+ #def traverse
290
+ # all_matches = []
291
+
292
+ # rules.each do |rule|
293
+ # rule = rule.strip
294
+ # next if rule.empty?
295
+ # source, db, target, conditions = rule.match(/([^\s]+)\s+([^\s]+)\s+([^\s]+)(?:\s+-\s+([^\s]+))?/).captures
296
+
297
+ # source_entities, target_entities = identify db, source, target
298
+
299
+ # matches = kb.subset(db, :source => source_entities, :target => target_entities)
300
+
301
+ # if conditions
302
+ # conditions.split(/\s+/).each do |condition|
303
+ # if condition.index "="
304
+ # key, value = conditions.split("=")
305
+ # matches = matches.select{|m| m.info[key.strip].to_s =~ /\b#{value.strip}\b/}
306
+ # else
307
+ # matches = matches.select{|m| m.info[condition.strip].to_s =~ /\btrue\b/}
308
+ # end
309
+ # end
310
+ # end
311
+
312
+ # reassign matches, source, target
313
+
314
+ # all_matches << matches
315
+ # end
316
+
317
+ # paths = find_paths rules, all_matches, assignments
318
+
319
+ # [assignments, paths]
320
+ #end
321
+
322
+ end
323
+
324
+ def traverse(rules, nopaths=false)
325
+ traverser = KnowledgeBase::Traverser.new self, rules
326
+ traverser.traverse nopaths
327
+ end
328
+
329
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'association'
2
+ require_relative 'association/item'
3
+ require_relative 'knowledge_base/registry'
4
+ require_relative 'knowledge_base/entity'
5
+ require_relative 'knowledge_base/query'
6
+ require_relative 'knowledge_base/traverse'
7
+ require_relative 'knowledge_base/list'
8
+ #require 'scout/knowledge_base/query'
9
+ #require 'scout/knowledge_base/syndicate'
10
+
11
+ class KnowledgeBase
12
+
13
+ attr_accessor :dir, :namespace, :registry, :entity_options, :format, :identifier_files
14
+
15
+ def initialize(dir, namespace = nil)
16
+ @dir = Path.setup(dir.dup)
17
+
18
+ @namespace = namespace
19
+
20
+ @identifier_files = []
21
+
22
+ @registry ||= IndiferentHash.setup({})
23
+ @entity_options ||= IndiferentHash.setup({})
24
+
25
+ @format ||= IndiferentHash.setup({})
26
+ @descriptions ||= IndiferentHash.setup({})
27
+ @indices ||= IndiferentHash.setup({})
28
+ end
29
+
30
+ def config_file(name)
31
+ @dir.config[name.to_s]
32
+ end
33
+
34
+ def save_variable(name)
35
+ file = config_file(name)
36
+ variable = "@#{name}".to_sym
37
+ Open.write(file, self.instance_variable_get(variable).to_yaml)
38
+ end
39
+
40
+ def load_variable(name)
41
+ file = config_file(name)
42
+ variable = "@#{name}".to_sym
43
+ self.instance_variable_set(variable, YAML.load(Open.read(file))) if file.exists?
44
+ end
45
+
46
+ def save
47
+ save_variable(:namespace)
48
+ save_variable(:registry)
49
+ save_variable(:entity_options)
50
+ save_variable(:identifier_files)
51
+ end
52
+
53
+ def load
54
+ load_variable(:namespace)
55
+ load_variable(:registry)
56
+ load_variable(:entity_options)
57
+ load_variable(:identifier_files)
58
+ end
59
+
60
+ def self.load(dir)
61
+ dir = Path.setup("var").knowledge_base[dir.to_s] if Symbol === dir
62
+ kb = KnowledgeBase.new dir
63
+ kb.load
64
+ kb
65
+ end
66
+
67
+ def info(name)
68
+
69
+ source = self.source(name)
70
+ target = self.target(name)
71
+ source_type = self.source_type(name)
72
+ target_type = self.target_type(name)
73
+ fields = self.fields(name)
74
+ source_entity_options = self.entity_options_for source_type, name
75
+ target_entity_options = self.entity_options_for target_type, name
76
+ undirected = self.undirected(name) == 'undirected'
77
+
78
+ info = {
79
+ :source => source,
80
+ :target => target,
81
+ :source_type => source_type,
82
+ :target_type => target_type,
83
+ :source_entity_options => source_entity_options,
84
+ :target_entity_options => target_entity_options,
85
+ :fields => fields,
86
+ :undirected => undirected,
87
+ }
88
+
89
+ info
90
+ end
91
+ end
@@ -48,12 +48,12 @@ module Annotation
48
48
 
49
49
  fields = fields.flatten.compact.uniq
50
50
 
51
- annotations = if Annotation.is_annotated?(objs)
52
- objs.annotations
51
+ annotations = if Annotation.is_annotated?(objs)
52
+ objs.annotation_hash.keys
53
53
  elsif (Array === objs && objs.any?)
54
54
  first = objs.compact.first
55
55
  if Annotation.is_annotated?(first)
56
- objs.compact.first.annotations
56
+ objs.compact.first.annotation_hash.keys
57
57
  else
58
58
  raise "Objects didn't have annotations"
59
59
  end
@@ -158,7 +158,7 @@ module Annotation
158
158
  Annotation.load_tsv_values(id, values, tsv.fields)
159
159
  end
160
160
 
161
- case tsv.key_field
161
+ case tsv.key_field
162
162
  when "List"
163
163
  annotated_objects.first
164
164
  else
@@ -66,8 +66,6 @@ module TSV
66
66
  index = TSV.setup({}, :type => :single)
67
67
  end
68
68
 
69
- tsv_file = TSV.open(tsv_file, **data_options) if ! TSV === tsv_file
70
-
71
69
  log_msg = "Index #{Log.fingerprint tsv_file} target #{Log.fingerprint target}"
72
70
  Log.low log_msg
73
71
  bar = log_msg if TrueClass === bar
@@ -7,7 +7,7 @@ module TSV
7
7
  if Proc === cast
8
8
  cast.call value
9
9
  else
10
- if value.nil? || value.empty?
10
+ if value.nil? || value == ""
11
11
  nil
12
12
  else
13
13
  value.send(cast)
@@ -1,5 +1,5 @@
1
1
  module TSV
2
- def self.paste_streams(streams, type: nil, sort: nil, sort_memory: nil, sep: nil, preamble: nil, header: nil, same_fields: nil, fix_flat: nil, all_match: nil, one2one: true, field_prefix: nil)
2
+ def self.paste_streams(streams, type: nil, sort: nil, sort_cmd_args: nil, sort_memory: nil, sep: nil, preamble: nil, header: nil, same_fields: nil, fix_flat: nil, all_match: nil, one2one: true, field_prefix: nil)
3
3
  sep = "\t" if sep.nil?
4
4
 
5
5
  streams = streams.collect do |stream|
@@ -20,7 +20,7 @@ module TSV
20
20
  num_streams = streams.length
21
21
 
22
22
  streams = streams.collect do |stream|
23
- Open.sort_stream(stream, memory: sort_memory)
23
+ Open.sort_stream(stream, memory: sort_memory, cmd_args: sort_cmd_args)
24
24
  end if sort
25
25
 
26
26
  begin
@@ -193,7 +193,7 @@ module TSV
193
193
  dumper.close
194
194
 
195
195
  streams.each do |stream|
196
- stream.close if stream.respond_to?(:close) && ! stream.closed?
196
+ stream.close if stream.respond_to?(:close)
197
197
  stream.join if stream.respond_to?(:join)
198
198
  end
199
199
  end
data/lib/scout/tsv.rb CHANGED
@@ -133,6 +133,8 @@ module TSV
133
133
 
134
134
  tsv.unnamed = unnamed unless unnamed.nil?
135
135
 
136
+ tsv.entity_options = entity_options
137
+
136
138
  tsv
137
139
  end
138
140
  end
@@ -50,6 +50,15 @@ class Step
50
50
  @info
51
51
  end
52
52
 
53
+ def pid
54
+ info[:pid]
55
+ end
56
+
57
+ def pid=(pid)
58
+ set_info :pid, pid
59
+ end
60
+
61
+
53
62
  def merge_info(new_info)
54
63
  info = self.info
55
64
  new_info.each do |key,value|
@@ -121,7 +130,7 @@ class Step
121
130
  if message.nil?
122
131
  Log.info [Log.color(:status, status, true), Log.color(:task, task_name, true), Log.color(:path, path)] * " "
123
132
  else
124
- message = Log.fingerprint(message).sub(/^'/,'').sub(/'$/,'')
133
+ message = Log.fingerprint(message.split("\n").first).sub(/^'/,'').sub(/'$/,'')
125
134
  Log.info [Log.color(:status, status, true), Log.color(:task, task_name, true), message, Log.color(:path, path)] * " "
126
135
  end
127
136
  end