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
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