scout-gear 10.7.1 → 10.7.3

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +50 -30
  3. data/VERSION +1 -1
  4. data/lib/scout/association/index.rb +5 -1
  5. data/lib/scout/association/item.rb +1 -1
  6. data/lib/scout/association.rb +46 -11
  7. data/lib/scout/entity/format.rb +9 -4
  8. data/lib/scout/entity/identifiers.rb +4 -4
  9. data/lib/scout/entity/named_array.rb +13 -0
  10. data/lib/scout/entity/property.rb +3 -1
  11. data/lib/scout/entity.rb +7 -4
  12. data/lib/scout/knowledge_base/enrichment.rb +9 -0
  13. data/lib/scout/knowledge_base/entity.rb +143 -0
  14. data/lib/scout/knowledge_base/list.rb +95 -0
  15. data/lib/scout/knowledge_base/query.rb +96 -0
  16. data/lib/scout/knowledge_base/registry.rb +173 -0
  17. data/lib/scout/knowledge_base/traverse.rb +329 -0
  18. data/lib/scout/knowledge_base.rb +91 -0
  19. data/lib/scout/persist/tsv/adapter/base.rb +13 -1
  20. data/lib/scout/persist/tsv.rb +2 -1
  21. data/lib/scout/tsv/annotation.rb +4 -4
  22. data/lib/scout/tsv/attach.rb +10 -2
  23. data/lib/scout/tsv/change_id.rb +3 -0
  24. data/lib/scout/tsv/dumper.rb +34 -30
  25. data/lib/scout/tsv/index.rb +0 -2
  26. data/lib/scout/tsv/open.rb +1 -0
  27. data/lib/scout/tsv/parser.rb +21 -10
  28. data/lib/scout/tsv/path.rb +8 -0
  29. data/lib/scout/tsv/stream.rb +17 -10
  30. data/lib/scout/tsv/traverse.rb +12 -2
  31. data/lib/scout/tsv/util/process.rb +4 -1
  32. data/lib/scout/tsv/util/select.rb +8 -2
  33. data/lib/scout/tsv/util/sort.rb +23 -15
  34. data/lib/scout/tsv/util.rb +11 -2
  35. data/lib/scout/tsv.rb +25 -11
  36. data/lib/scout/workflow/definition.rb +3 -3
  37. data/lib/scout/workflow/deployment/orchestrator.rb +8 -5
  38. data/lib/scout/workflow/step/dependencies.rb +35 -11
  39. data/lib/scout/workflow/step/file.rb +2 -1
  40. data/lib/scout/workflow/step/info.rb +23 -2
  41. data/lib/scout/workflow/step/load.rb +5 -3
  42. data/lib/scout/workflow/step/progress.rb +6 -0
  43. data/lib/scout/workflow/step/provenance.rb +1 -1
  44. data/lib/scout/workflow/step/status.rb +10 -4
  45. data/lib/scout/workflow/step.rb +32 -12
  46. data/lib/scout/workflow/task/dependencies.rb +33 -24
  47. data/lib/scout/workflow/task/inputs.rb +40 -12
  48. data/lib/scout/workflow/task.rb +22 -10
  49. data/lib/scout/workflow/usage.rb +2 -2
  50. data/lib/scout/workflow.rb +1 -1
  51. data/scout-gear.gemspec +28 -4
  52. data/scout_commands/kb/config +33 -0
  53. data/scout_commands/kb/entities +35 -0
  54. data/scout_commands/kb/list +39 -0
  55. data/scout_commands/kb/query +78 -0
  56. data/scout_commands/kb/register +44 -0
  57. data/scout_commands/kb/show +37 -0
  58. data/scout_commands/kb/traverse +66 -0
  59. data/test/data/person/brothers +1 -1
  60. data/test/scout/entity/test_identifiers.rb +3 -3
  61. data/test/scout/entity/test_named_array.rb +21 -0
  62. data/test/scout/knowledge_base/test_enrichment.rb +0 -0
  63. data/test/scout/knowledge_base/test_entity.rb +38 -0
  64. data/test/scout/knowledge_base/test_list.rb +40 -0
  65. data/test/scout/knowledge_base/test_query.rb +39 -0
  66. data/test/scout/knowledge_base/test_registry.rb +16 -0
  67. data/test/scout/knowledge_base/test_traverse.rb +245 -0
  68. data/test/scout/persist/test_tsv.rb +20 -0
  69. data/test/scout/persist/tsv/adapter/test_base.rb +20 -0
  70. data/test/scout/test_association.rb +17 -3
  71. data/test/scout/test_entity.rb +0 -15
  72. data/test/scout/test_knowledge_base.rb +27 -0
  73. data/test/scout/test_tsv.rb +40 -0
  74. data/test/scout/tsv/test_dumper.rb +24 -0
  75. data/test/scout/tsv/test_path.rb +24 -0
  76. data/test/scout/tsv/test_stream.rb +93 -0
  77. data/test/scout/tsv/test_traverse.rb +99 -0
  78. data/test/scout/tsv/test_util.rb +2 -0
  79. data/test/scout/tsv/util/test_select.rb +22 -0
  80. data/test/scout/tsv/util/test_sort.rb +24 -0
  81. data/test/scout/workflow/step/test_dependencies.rb +26 -0
  82. data/test/scout/workflow/step/test_info.rb +35 -0
  83. data/test/scout/workflow/task/test_dependencies.rb +67 -1
  84. data/test/scout/workflow/task/test_inputs.rb +24 -7
  85. data/test/scout/workflow/test_task.rb +36 -0
  86. data/test/scout/workflow/test_usage.rb +0 -1
  87. data/test/test_helper.rb +17 -0
  88. metadata +27 -3
@@ -0,0 +1,96 @@
1
+ require_relative 'entity'
2
+
3
+ class KnowledgeBase
4
+
5
+ def setup(name, matches, reverse = false)
6
+ AssociationItem.setup matches, self, name, reverse
7
+ end
8
+
9
+ def _subset(name, source = :all, target = :all, options = {})
10
+ repo = get_index name, options
11
+
12
+ repo.subset(source, target)
13
+ end
14
+
15
+ def subset(name, entities, options = {}, &block)
16
+ entities, options = options, entities if entities.nil? and Hash === options
17
+
18
+ entities = case entities
19
+ when :all
20
+ {:target => :all, :source => :all}
21
+ when AnnotatedArray
22
+ format = entities.format if entities.respond_to? :format
23
+ format ||= entities.base_entity.to_s
24
+ {format => entities.purge}
25
+ when Hash
26
+ entities
27
+ else
28
+ raise "Entities are not a Hash or an AnnotatedArray: #{Log.fingerprint entities}"
29
+ end
30
+
31
+ identify, identify_source, identify_target = entities.merge(options || {}).values_at :identify, :identify_source, :identify_target
32
+
33
+ source, target = select_entities(name, entities, options)
34
+
35
+ source = identify_source(name, source) if identify_source
36
+ target = identify_target(name, target) if identify_target
37
+
38
+ source = identify(name, source) if identify && !identify_source
39
+ target = identify(name, target) if identify && !identify_target
40
+
41
+ return [] if source.nil? or target.nil?
42
+ return [] if Array === target and target.empty?
43
+ return [] if Array === source and source.empty?
44
+
45
+ matches = _subset name, source, target, options
46
+
47
+ setup(name, matches)
48
+
49
+ matches = matches.select(&block) if block_given?
50
+
51
+ matches
52
+ end
53
+
54
+ def all(name, options={})
55
+ repo = get_index name, options
56
+ setup name, repo.keys
57
+ end
58
+
59
+ def _children(name, entity)
60
+ repo = get_index name
61
+ repo.match(entity)
62
+ end
63
+
64
+ def children(name, entity)
65
+ entity = identify_source(name, entity)
66
+ setup(name, _children(name, entity))
67
+ end
68
+
69
+ def _parents(name, entity)
70
+ repo = get_index name
71
+ repo.reverse.match(entity)
72
+ end
73
+
74
+ def parents(name, entity)
75
+ entity = identify_target(name, entity)
76
+ matches = _parents(name, entity)
77
+ #matches.each{|m| m.replace(m.partition("~").reverse*"") } unless undirected(name)
78
+ setup(name, matches, true)
79
+ end
80
+
81
+ def _neighbours(name, entity)
82
+ if undirected(name) and source(name) == target(name)
83
+ {:children => _children(name, entity)}
84
+ else
85
+ {:parents => _parents(name, entity), :children => _children(name, entity)}
86
+ end
87
+ end
88
+
89
+ def neighbours(name, entity)
90
+ hash = _neighbours(name, entity)
91
+ IndiferentHash.setup(hash)
92
+ setup(name, hash[:children]) if hash[:children]
93
+ setup(name, hash[:parents], true) if hash[:parents]
94
+ hash
95
+ end
96
+ end
@@ -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