scout-gear 10.7.2 → 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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1182999631b54e94f842c90017e1f97dde904ddafe47129cd80b51097714a1c4
4
- data.tar.gz: ec736a5762fc2e58450a7955dffffe61eb73584f0bdc53bf3cd368e194ae7cbf
3
+ metadata.gz: 469b6e128a39e5612de5698005de8f4859c015bf3d65a5490a58f2f9f0312a39
4
+ data.tar.gz: 0a76edd86052c35af31f6de227c0bc43c71bb6b64fdc757e1179bf88a4ce8701
5
5
  SHA512:
6
- metadata.gz: 66a5c6ab90c2aa047cfbd000731eaec3913994880adddc9ac2daac9802ee2ebef14a054c1e292200624a6411ac2244d5f915de8d3ed9113d1237edc3795ab027
7
- data.tar.gz: 27a0e87ac1360420bffc87b34b01eb6f21631fdf3f6ef95983fb46152f6e0e390200518dd2bebcc95c880e74d6f6f8a98a76577be2fbad0aea7d1fbe9c0720b1
6
+ metadata.gz: c8753c031fda58c461afdefa3412a0d7996c6d5139a30653b1e6fcfaeba7fdf31b4d300bb2cb07060e7eb12d63891fc7a157cec3964c59d5cc68584ffc8823c8
7
+ data.tar.gz: 05cbb772edf0384f2889ad1be2cc597425d071da8c488364a8c72b23e6b7098aea0089247c180da09165f3eb7d899de494dca9ce2a00f285dcfaa564c8dc3e68
data/.vimproject CHANGED
@@ -38,38 +38,11 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
38
38
  orchestrator.rb
39
39
  }
40
40
  }
41
- semaphore.rb
42
41
  work_queue.rb
43
42
  work_queue=work_queue{
44
43
  socket.rb
45
44
  worker.rb
46
45
  }
47
-
48
- persist=persist{
49
- engine.rb
50
- engine=engine{
51
- tokyocabinet.rb
52
- fix_width_table.rb
53
- tkrzw.rb
54
- packed_index.rb
55
- sharder.rb
56
- }
57
- tsv.rb
58
- tsv=tsv{
59
- adapter.rb
60
- serialize.rb
61
- adapter=adapter{
62
- base.rb
63
-
64
- fix_width_table.rb
65
- packed_index.rb
66
- tkrzw.rb
67
- tokyocabinet.rb
68
- sharder.rb
69
- }
70
- }
71
- }
72
-
73
46
  tsv.rb
74
47
  tsv=tsv{
75
48
  util.rb
@@ -101,6 +74,30 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
101
74
  open.rb
102
75
  csv.rb
103
76
  }
77
+ persist=persist{
78
+ engine.rb
79
+ engine=engine{
80
+ tokyocabinet.rb
81
+ fix_width_table.rb
82
+ tkrzw.rb
83
+ packed_index.rb
84
+ sharder.rb
85
+ }
86
+ tsv.rb
87
+ tsv=tsv{
88
+ adapter.rb
89
+ serialize.rb
90
+ adapter=adapter{
91
+ base.rb
92
+
93
+ fix_width_table.rb
94
+ packed_index.rb
95
+ tkrzw.rb
96
+ tokyocabinet.rb
97
+ sharder.rb
98
+ }
99
+ }
100
+ }
104
101
  entity.rb
105
102
  entity=entity{
106
103
  property.rb
@@ -111,17 +108,26 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
111
108
  }
112
109
  association.rb
113
110
  association=association{
114
- fields.rb
115
111
  index.rb
112
+ fields.rb
116
113
  item.rb
117
114
  }
118
-
115
+ knowledge_base.rb
116
+ knowledge_base=knowledge_base{
117
+ registry.rb
118
+ entity.rb
119
+ query.rb
120
+ traverse.rb
121
+ enrichment.rb
122
+ list.rb
123
+ }
119
124
  offsite.rb
120
125
  offsite=offsite{
121
126
  ssh.rb
122
127
  sync.rb
123
128
  step.rb
124
129
  }
130
+ semaphore.rb
125
131
  }
126
132
  scout-gear.rb
127
133
  workflow-scout.rb
@@ -138,10 +144,14 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
138
144
  update
139
145
  template
140
146
  offsite
141
- db=db{
147
+ kb=kb{
148
+ config
149
+ entities
142
150
  register
143
- query
144
151
  show
152
+ query
153
+ traverse
154
+ list
145
155
  }
146
156
  workflow=workflow{
147
157
  task
@@ -164,8 +174,12 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
164
174
  test_helper.rb
165
175
  test_scout-gear.rb
166
176
  test_scout.rb
167
- data=data{
177
+ data=data filter="*"{
168
178
  person=person{
179
+ brothers
180
+ identifiers
181
+ marriages
182
+ parents
169
183
  }
170
184
  }
171
185
  scout=scout{
data/VERSION CHANGED
@@ -1 +1 @@
1
- 10.7.2
1
+ 10.7.3
@@ -2,6 +2,10 @@ require 'scout/annotation'
2
2
  module Association
3
3
 
4
4
  def self.index(file, source: nil, target: nil, source_format: nil, target_format: nil, format: nil, **kwargs)
5
+ IndiferentHash.setup(kwargs)
6
+ source = kwargs.delete :source if kwargs.include?(:source)
7
+ target = kwargs.delete :target if kwargs.include?(:target)
8
+
5
9
  persist_options = IndiferentHash.pull_keys kwargs, :persist
6
10
  index_persist_options = IndiferentHash.add_defaults persist_options.dup, persist: true,
7
11
  prefix: "Association::Index",
@@ -102,7 +102,7 @@ module AssociationItem
102
102
  property :info => :array2single do
103
103
  fields = self.info_fields
104
104
 
105
- return [{}] * self.length if fields.nil? or fields.empty?
105
+ next [{}] * self.length if fields.nil? or fields.empty?
106
106
 
107
107
  value = self.value
108
108
  value.collect{|v|
@@ -6,7 +6,9 @@ require_relative 'association/item'
6
6
 
7
7
  module Association
8
8
  def self.open(obj, source: nil, target: nil, fields: nil, source_format: nil, target_format: nil, format: nil, **kwargs)
9
-
9
+ IndiferentHash.setup(kwargs)
10
+ source = kwargs.delete :source if kwargs.include?(:source)
11
+ target = kwargs.delete :target if kwargs.include?(:target)
10
12
 
11
13
  if Path.is_filename?(obj)
12
14
  options = TSV.parse_options(obj).merge(kwargs)
@@ -29,9 +31,12 @@ module Association
29
31
 
30
32
  type, identifiers = IndiferentHash.process_options options, :type, :identifiers
31
33
 
32
- if source_format
34
+ if source_format || target_format
33
35
  translation_files = [TSV.identifier_files(obj), Entity.identifier_files(source_format), identifiers].flatten.compact
34
- translation_files.collect!{|f| Path.is_filename?(f, false) ? Path.setup(f.gsub(/\[?NAMESPACE\]?/, options[:namespace])) : f }
36
+ translation_files.collect!{|f| (Path.is_filename?(f, false) && options[:namespace]) ? Path.setup(f.gsub(/\[?NAMESPACE\]?/, options[:namespace])) : f }
37
+ end
38
+
39
+ if source_format
35
40
  source_index = begin
36
41
  TSV.translation_index(translation_files, source_header, source_format)
37
42
  rescue
@@ -40,8 +45,6 @@ module Association
40
45
  end
41
46
 
42
47
  if target_format
43
- translation_files = [TSV.identifier_files(obj), Entity.identifier_files(target_format), identifiers].flatten.compact
44
- translation_files.collect!{|f| Path.is_filename?(f, false) ? Path.setup(f.gsub(/\[?NAMESPACE\]?/, options[:namespace])) : f }
45
48
  target_index = begin
46
49
  TSV.translation_index(translation_files, field_headers.first, target_format)
47
50
  rescue
@@ -82,7 +85,7 @@ module Association
82
85
 
83
86
  if source_index.nil? && target_index.nil?
84
87
  if TSV === obj
85
- IndiferentHash.pull_keys kwargs, :persist
88
+ IndiferentHash.pull_keys options, :persist
86
89
  type = options[:type] || obj.type
87
90
  res = obj.reorder original_source_header, all_fields.values_at(*field_pos), **options.merge(type: type, merge: true)
88
91
  else
@@ -109,8 +112,24 @@ module Association
109
112
  transformer
110
113
  end
111
114
 
112
- def self.database(*args, **kwargs)
113
- tsv = open(*args, **kwargs)
114
- TSV::Transformer === tsv ? tsv.tsv(merge: true) : tsv
115
+ def self.database(file, *args, **kwargs)
116
+ persist_options = IndiferentHash.pull_keys kwargs, :persist
117
+
118
+ database_persist_options = IndiferentHash.add_defaults persist_options.dup, persist: true,
119
+ prefix: "Association::Index", serializer: :list,
120
+ other_options: kwargs
121
+
122
+ Persist.tsv(file, kwargs, engine: "BDB", persist_options: database_persist_options) do |data|
123
+ tsv = open(file, *args, **kwargs)
124
+ if TSV::Transformer === tsv
125
+ tsv.tsv(merge: true, data: data)
126
+ elsif data.respond_to?(:persistence_path)
127
+ data.merge!(tsv)
128
+ tsv.annotate(data)
129
+ data
130
+ else
131
+ tsv
132
+ end
133
+ end
115
134
  end
116
135
  end
@@ -1,6 +1,6 @@
1
1
  module Entity
2
2
  def self.identifier_files(field)
3
- entity_type = Entity.formats[field]
3
+ entity_type = Entity.formats[Entity.formats.find(field)]
4
4
  return [] unless entity_type and entity_type.include? Entity::Identified
5
5
  entity_type.identifier_files
6
6
  end
@@ -48,7 +48,7 @@ module Entity
48
48
  def identifier_files
49
49
  files = identity_type.identifier_files.dup
50
50
  return [] if files.nil?
51
- files.collect!{|f| f.annotate f.gsub(/\b#{NAMESPACE_TAG}\b/, namespace.to_s) } if annotations.include? :namespace and self.namespace
51
+ files.collect!{|f| f.annotate f.gsub(/\b#{NAMESPACE_TAG}\b/, namespace.to_s) } if annotation_hash.include? :namespace and self.namespace
52
52
  if files.select{|f| f =~ /\b#{NAMESPACE_TAG}\b/ }.any?
53
53
  Log.warn "Rejecting some identifier files for lack of 'namespace': " << files.select{|f| f =~ /\b#{NAMESPACE_TAG}\b/ } * ", "
54
54
  end
@@ -1,3 +1,4 @@
1
+ require 'scout/exceptions'
1
2
  module Entity
2
3
  class << self
3
4
  attr_accessor :entity_property_cache
@@ -0,0 +1,9 @@
1
+ require 'rbbt/knowledge_base/registry'
2
+ class KnowledgeBase
3
+ def enrichment(name, entities, options = {})
4
+ require 'rbbt/statistics/hypergeometric'
5
+ database = get_database(name, options)
6
+ entities = identify_source name, entities
7
+ database.enrichment entities, database.fields.first, :persist => false
8
+ end
9
+ end
@@ -0,0 +1,143 @@
1
+ require_relative '../entity'
2
+
3
+ class KnowledgeBase
4
+
5
+ def select_entities(name, entities, options = {})
6
+ index = get_index(name, options)
7
+
8
+ source_field = index.source_field
9
+ target_field = index.target_field
10
+
11
+ source_type = source_type name
12
+ target_type = target_type name
13
+
14
+ source_entities = entities[:source] || entities[source_field] || entities[Entity.formats[source_field].to_s] || entities[:both]
15
+ target_entities = entities[:target] || entities[target_field] || entities[Entity.formats[target_field].to_s] || entities[:both]
16
+
17
+ [source_entities, target_entities]
18
+ end
19
+
20
+ def entity_options_for(type, database_name = nil)
21
+ entity_options = self.entity_options
22
+ IndiferentHash.setup entity_options if entity_options and not IndiferentHash === entity_options
23
+ options = entity_options[type.to_s] || entity_options[Entity.formats[type].to_s] || {}
24
+ options[:format] = @format[type] if Hash === @format && @format.include?(type)
25
+ namespace = self.namespace
26
+ namespace = db_namespace(database_name) if namespace.nil? and database_name
27
+ if database_name
28
+ database = get_database(database_name)
29
+ if database.entity_options and (database.entity_options[type] or database.entity_options[Entity.formats[type.to_s].to_s])
30
+ options = options.merge(database.entity_options[type] || database.entity_options[Entity.formats[type.to_s].to_s])
31
+ end
32
+ end
33
+ options
34
+ end
35
+
36
+ def annotate(entities, type, database = nil)
37
+ format = @format[type] || type
38
+ entity_options = entity_options_for(type, database)
39
+ Entity.prepare_entity(entities, format, entity_options)
40
+ end
41
+
42
+ def translate(entities, type)
43
+ if format = @format[type] and (entities.respond_to? :format and format != entities.format)
44
+ entities.to format
45
+ else
46
+ entities
47
+ end
48
+ end
49
+
50
+ def source_type(name)
51
+ Entity.formats[Entity.formats.find(source(name))]
52
+ end
53
+
54
+ def target_type(name)
55
+ Entity.formats[Entity.formats.find(target(name))]
56
+ end
57
+
58
+ def entities
59
+ all_databases.inject([]){|acc,name| acc << source(name); acc << target(name)}.uniq
60
+ end
61
+
62
+ def entity_types
63
+ entities.collect{|entity| Entity.formats[entity] }.uniq
64
+ end
65
+
66
+ def identifier_files(name)
67
+ get_database(name).identifier_files.dup + self.identifier_files
68
+ end
69
+
70
+ def db_namespace(name)
71
+ get_database(name).namespace
72
+ end
73
+
74
+ def source_index(name)
75
+ Persist.memory("Source index #{name}: KB directory #{dir}") do
76
+ identifier_files = identifier_files(name)
77
+ identifier_files.concat Entity.identifier_files(source(name)) if defined? Entity
78
+ identifier_files.uniq!
79
+ identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, namespace))} if namespace
80
+ identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, db_namespace(name)))} if not namespace and db_namespace(name)
81
+ identifier_files.reject!{|f| f.match(/\bNAMESPACE\b/)}
82
+ TSV.translation_index identifier_files, nil, source(name), :persist => true
83
+ end
84
+ end
85
+
86
+ def target_index(name)
87
+ Persist.memory("Target index #{name}: KB directory #{dir}") do
88
+ identifier_files = identifier_files(name)
89
+ identifier_files.concat Entity.identifier_files(target(name)) if defined? Entity
90
+ identifier_files.uniq!
91
+ identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, namespace))} if self.namespace
92
+ identifier_files.collect!{|f| f.annotate(f.gsub(/\bNAMESPACE\b/, db_namespace(name)))} if namespace.nil? and db_namespace(name)
93
+ identifier_files.reject!{|f| f.match(/\bNAMESPACE\b/)}
94
+ TSV.translation_index identifier_files, nil, target(name), :persist => true
95
+ end
96
+ end
97
+
98
+ def identify_source(name, entity)
99
+ return :all if entity == :all
100
+ index = begin source_index(name) rescue nil end
101
+ return entity if index.nil?
102
+ if Array === entity
103
+ entity.collect{|e| index[e] || e }
104
+ else
105
+ index[entity] || entity
106
+ end
107
+ end
108
+
109
+ def identify_target(name, entity)
110
+ return :all if entity == :all
111
+ index = begin target_index(name) rescue nil end
112
+ return entity if index.nil?
113
+ if Array === entity
114
+ entity.collect{|e| index[e] || e }
115
+ else
116
+ index[entity] || entity
117
+ end
118
+ end
119
+
120
+ def identify(name, entity)
121
+ identify_source(name, entity) || identify_target(name, entity)
122
+ end
123
+
124
+ def define_entity_modules
125
+ entity_options.each do |entity,options|
126
+ next unless options[:identifiers]
127
+ identifiers = options[:identifiers]
128
+ identifiers = identifiers.split(",") unless Array === identifiers
129
+ m = begin
130
+ Object.const_get entity
131
+ rescue
132
+ m = Module.new
133
+ m.extend Entity
134
+ m.include Entity::Identified
135
+ Object.const_set entity, m
136
+ end
137
+
138
+ identifiers.each do |file|
139
+ m.add_identifiers Path.setup(file), self.format
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,95 @@
1
+ require 'scout/annotation'
2
+ class KnowledgeBase
3
+
4
+ def list_file(id, entity_type = nil)
5
+ id = Path.sanitize_filename(id)
6
+
7
+ entity_type = entity_type.to_s.split(":").last
8
+
9
+ raise "Ilegal list id: #{ id }" unless Misc.path_relative_to(dir, File.join(dir, id))
10
+
11
+ if entity_type
12
+ if entity_type.to_s == "simple"
13
+ path = dir.lists[entity_type.to_s][id]
14
+ else
15
+ path = dir.lists[entity_type.to_s][id + ".tsv"]
16
+ end
17
+ else
18
+ path = dir.lists.glob("*/#{id}").first
19
+ path ||= dir.lists.glob("*/#{id}.tsv").first
20
+ raise "List not found #{id}" if path.nil?
21
+ end
22
+
23
+ path.find
24
+ end
25
+
26
+ def save_list(id, list)
27
+ if AnnotatedArray === list
28
+ path = list_file(id, list.base_entity)
29
+ else
30
+ path = list_file(id, :simple)
31
+ end
32
+
33
+ Open.lock path do
34
+ begin
35
+ if AnnotatedArray === list
36
+ Open.write(path, Annotation.tsv(list, :all).to_s)
37
+ else
38
+ Open.write(path, list * "\n")
39
+ end
40
+ rescue
41
+ FileUtils.rm(path) if File.exist?(path)
42
+ raise $!
43
+ end
44
+ end
45
+ end
46
+
47
+ def load_list(id, entity_type = nil)
48
+ if entity_type
49
+ path = list_file(id, entity_type)
50
+ path = list_file(id) unless path.exists?
51
+ else
52
+ path = list_file(id)
53
+ end
54
+
55
+ raise "List not found: #{ id }" unless path and path.exists?
56
+
57
+ begin
58
+ if path.get_extension == 'tsv'
59
+ list = Annotation.load_tsv path.tsv
60
+ list.extend AnnotatedArray
61
+ list
62
+ else
63
+ path.list
64
+ end
65
+ rescue
66
+ Log.exception $!
67
+ nil
68
+ end
69
+ end
70
+
71
+ def lists
72
+ lists = {}
73
+ dir.lists.glob("*").each do |list_dir|
74
+ lists[list_dir.basename] = list_dir.glob("*").
75
+ collect(&:unset_extension).
76
+ collect(&:basename)
77
+ end
78
+ lists
79
+ end
80
+
81
+ def delete_list(id, entity_type = nil)
82
+ path = list_file(id, entity_type)
83
+ path = list_file(id) unless path.exists?
84
+
85
+ "This list does not belong to #{ user }: #{[entity_type, id] * ": "}" unless File.exist? path
86
+
87
+ Open.lock path do
88
+ begin
89
+ FileUtils.rm path if File.exist? path
90
+ rescue
91
+ raise $!
92
+ end
93
+ end
94
+ end
95
+ end
@@ -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