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.
- checksums.yaml +4 -4
- data/.vimproject +50 -30
- data/VERSION +1 -1
- data/lib/scout/association/index.rb +5 -1
- data/lib/scout/association/item.rb +1 -1
- data/lib/scout/association.rb +46 -11
- data/lib/scout/entity/format.rb +9 -4
- data/lib/scout/entity/identifiers.rb +4 -4
- data/lib/scout/entity/named_array.rb +13 -0
- data/lib/scout/entity/property.rb +3 -1
- data/lib/scout/entity.rb +7 -4
- data/lib/scout/knowledge_base/enrichment.rb +9 -0
- data/lib/scout/knowledge_base/entity.rb +143 -0
- data/lib/scout/knowledge_base/list.rb +95 -0
- data/lib/scout/knowledge_base/query.rb +96 -0
- data/lib/scout/knowledge_base/registry.rb +173 -0
- data/lib/scout/knowledge_base/traverse.rb +329 -0
- data/lib/scout/knowledge_base.rb +91 -0
- data/lib/scout/persist/tsv/adapter/base.rb +13 -1
- data/lib/scout/persist/tsv.rb +2 -1
- data/lib/scout/tsv/annotation.rb +4 -4
- data/lib/scout/tsv/attach.rb +10 -2
- data/lib/scout/tsv/change_id.rb +3 -0
- data/lib/scout/tsv/dumper.rb +34 -30
- data/lib/scout/tsv/index.rb +0 -2
- data/lib/scout/tsv/open.rb +1 -0
- data/lib/scout/tsv/parser.rb +21 -10
- data/lib/scout/tsv/path.rb +8 -0
- data/lib/scout/tsv/stream.rb +17 -10
- data/lib/scout/tsv/traverse.rb +12 -2
- data/lib/scout/tsv/util/process.rb +4 -1
- data/lib/scout/tsv/util/select.rb +8 -2
- data/lib/scout/tsv/util/sort.rb +23 -15
- data/lib/scout/tsv/util.rb +11 -2
- data/lib/scout/tsv.rb +25 -11
- data/lib/scout/workflow/definition.rb +3 -3
- data/lib/scout/workflow/deployment/orchestrator.rb +8 -5
- data/lib/scout/workflow/step/dependencies.rb +35 -11
- data/lib/scout/workflow/step/file.rb +2 -1
- data/lib/scout/workflow/step/info.rb +23 -2
- data/lib/scout/workflow/step/load.rb +5 -3
- data/lib/scout/workflow/step/progress.rb +6 -0
- data/lib/scout/workflow/step/provenance.rb +1 -1
- data/lib/scout/workflow/step/status.rb +10 -4
- data/lib/scout/workflow/step.rb +32 -12
- data/lib/scout/workflow/task/dependencies.rb +33 -24
- data/lib/scout/workflow/task/inputs.rb +40 -12
- data/lib/scout/workflow/task.rb +22 -10
- data/lib/scout/workflow/usage.rb +2 -2
- data/lib/scout/workflow.rb +1 -1
- data/scout-gear.gemspec +28 -4
- data/scout_commands/kb/config +33 -0
- data/scout_commands/kb/entities +35 -0
- data/scout_commands/kb/list +39 -0
- data/scout_commands/kb/query +78 -0
- data/scout_commands/kb/register +44 -0
- data/scout_commands/kb/show +37 -0
- data/scout_commands/kb/traverse +66 -0
- data/test/data/person/brothers +1 -1
- data/test/scout/entity/test_identifiers.rb +3 -3
- data/test/scout/entity/test_named_array.rb +21 -0
- data/test/scout/knowledge_base/test_enrichment.rb +0 -0
- data/test/scout/knowledge_base/test_entity.rb +38 -0
- data/test/scout/knowledge_base/test_list.rb +40 -0
- data/test/scout/knowledge_base/test_query.rb +39 -0
- data/test/scout/knowledge_base/test_registry.rb +16 -0
- data/test/scout/knowledge_base/test_traverse.rb +245 -0
- data/test/scout/persist/test_tsv.rb +20 -0
- data/test/scout/persist/tsv/adapter/test_base.rb +20 -0
- data/test/scout/test_association.rb +17 -3
- data/test/scout/test_entity.rb +0 -15
- data/test/scout/test_knowledge_base.rb +27 -0
- data/test/scout/test_tsv.rb +40 -0
- data/test/scout/tsv/test_dumper.rb +24 -0
- data/test/scout/tsv/test_path.rb +24 -0
- data/test/scout/tsv/test_stream.rb +93 -0
- data/test/scout/tsv/test_traverse.rb +99 -0
- data/test/scout/tsv/test_util.rb +2 -0
- data/test/scout/tsv/util/test_select.rb +22 -0
- data/test/scout/tsv/util/test_sort.rb +24 -0
- data/test/scout/workflow/step/test_dependencies.rb +26 -0
- data/test/scout/workflow/step/test_info.rb +35 -0
- data/test/scout/workflow/task/test_dependencies.rb +67 -1
- data/test/scout/workflow/task/test_inputs.rb +24 -7
- data/test/scout/workflow/test_task.rb +36 -0
- data/test/scout/workflow/test_usage.rb +0 -1
- data/test/test_helper.rb +17 -0
- 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
|