accord 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +43 -0
- data/accord.gemspec +20 -0
- data/lib/accord.rb +4 -0
- data/lib/accord/adapter_registry.rb +106 -0
- data/lib/accord/base_registry.rb +73 -0
- data/lib/accord/declarations.rb +131 -0
- data/lib/accord/exceptions.rb +22 -0
- data/lib/accord/extendor.rb +36 -0
- data/lib/accord/extendor_container.rb +43 -0
- data/lib/accord/interface.rb +189 -0
- data/lib/accord/interface_body.rb +73 -0
- data/lib/accord/interface_members.rb +31 -0
- data/lib/accord/interface_method.rb +27 -0
- data/lib/accord/interfaces.rb +471 -0
- data/lib/accord/nested_key_hash.rb +66 -0
- data/lib/accord/ro.rb +65 -0
- data/lib/accord/signature_info.rb +76 -0
- data/lib/accord/specification.rb +83 -0
- data/lib/accord/subscription_registry.rb +76 -0
- data/lib/accord/tags.rb +25 -0
- data/lib/accord/version.rb +3 -0
- data/spec/adapter_registry_spec.rb +296 -0
- data/spec/declarations_spec.rb +144 -0
- data/spec/extendor_container_spec.rb +101 -0
- data/spec/extendor_spec.rb +203 -0
- data/spec/integration/adaptation_spec.rb +86 -0
- data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
- data/spec/integration/adapter_hook_spec.rb +41 -0
- data/spec/integration/default_adapters_spec.rb +81 -0
- data/spec/integration/hash_adapters_spec.rb +20 -0
- data/spec/integration/interface_declaration_spec.rb +258 -0
- data/spec/integration/multi_adapters_spec.rb +83 -0
- data/spec/integration/named_adapters_spec.rb +54 -0
- data/spec/integration/single_adapters_spec.rb +93 -0
- data/spec/integration/subscriptions_spec.rb +245 -0
- data/spec/integration/verification_spec.rb +215 -0
- data/spec/interface_body_spec.rb +157 -0
- data/spec/interface_members_spec.rb +57 -0
- data/spec/interface_spec.rb +147 -0
- data/spec/nested_key_hash_spec.rb +140 -0
- data/spec/signature_info_spec.rb +65 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/specification_spec.rb +246 -0
- data/spec/subscription_registry_spec.rb +206 -0
- data/spec/tags_spec.rb +38 -0
- metadata +134 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Accord
|
2
|
+
class NestedKeyHash
|
3
|
+
def [](keys)
|
4
|
+
keys = keys.dup
|
5
|
+
last_key = keys.pop
|
6
|
+
last_hash = keys.inject(hash) do |partial, key|
|
7
|
+
return nil unless partial.has_key?(key)
|
8
|
+
partial[key]
|
9
|
+
end
|
10
|
+
last_hash[last_key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(keys, value)
|
14
|
+
keys = keys.dup
|
15
|
+
last_key = keys.pop
|
16
|
+
last_hash = keys.inject(hash) { |partial, key| partial[key] ||= {} }
|
17
|
+
last_hash[last_key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(keys)
|
21
|
+
keys = keys.dup
|
22
|
+
last_hash = {}
|
23
|
+
result = nil
|
24
|
+
while last_hash.size == 0 && keys.any?
|
25
|
+
last_key = keys.pop
|
26
|
+
last_hash = keys.inject(hash) { |partial, key| partial[key] || {} }
|
27
|
+
partial_result = last_hash.delete(last_key)
|
28
|
+
result ||= partial_result
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def detect_expansion(keys)
|
34
|
+
keys.inject(hash) do |partial, part|
|
35
|
+
expansion = yield(part)
|
36
|
+
valid_key = expansion.detect { |key| partial.has_key?(key) }
|
37
|
+
return unless valid_key
|
38
|
+
partial[valid_key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_expansions(keys, partial=hash, results=[], &block)
|
43
|
+
if keys.size == 1
|
44
|
+
expansion = block.call(keys.first)
|
45
|
+
valid_expansions = expansion.select { |key| partial.has_key?(key) }
|
46
|
+
valid_expansions.each { |key| results << partial[key] }
|
47
|
+
return results
|
48
|
+
end
|
49
|
+
keys = keys.dup
|
50
|
+
first_key = keys.shift
|
51
|
+
expansion = block.call(first_key)
|
52
|
+
valid_expansions = expansion.select { |key| partial.has_key?(key) }
|
53
|
+
return results unless valid_expansions.any?
|
54
|
+
valid_expansions.each do |key|
|
55
|
+
select_expansions(keys, partial[key], results, &block)
|
56
|
+
end
|
57
|
+
results
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def hash
|
63
|
+
@hash ||= {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/accord/ro.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Accord
|
2
|
+
class RO
|
3
|
+
CACHE_IVAR_NAME = :@_accord_ro_cache_
|
4
|
+
|
5
|
+
def initialize(item, &block)
|
6
|
+
@item = item
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve
|
11
|
+
cached = from_cache
|
12
|
+
return cached if cached
|
13
|
+
|
14
|
+
bases = block.call(item)
|
15
|
+
bases_ros = bases.map { |b| RO.new(b, &block).resolve }
|
16
|
+
merge([[item]] + bases_ros + [bases]).tap { |ro| cache(ro) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def changed
|
20
|
+
invalidate
|
21
|
+
resolve
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :item
|
27
|
+
attr_reader :block
|
28
|
+
|
29
|
+
def from_cache
|
30
|
+
item.instance_variable_get(CACHE_IVAR_NAME)
|
31
|
+
end
|
32
|
+
|
33
|
+
def cache(ro)
|
34
|
+
item.instance_variable_set(CACHE_IVAR_NAME, ro)
|
35
|
+
end
|
36
|
+
|
37
|
+
def invalidate
|
38
|
+
item.instance_variable_set(CACHE_IVAR_NAME, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge(sequences)
|
42
|
+
sequences = sequences.map { |seq| seq.dup }
|
43
|
+
result = []
|
44
|
+
|
45
|
+
loop do
|
46
|
+
sequences.delete_if { |sequence| sequence.empty? }
|
47
|
+
return result unless sequences.any?
|
48
|
+
|
49
|
+
good = sequences.detect do |current|
|
50
|
+
sequences.all? { |seq| !tail(seq).include?(current.first) }
|
51
|
+
end
|
52
|
+
|
53
|
+
raise TypeError, "inconsistent hierarchy" unless good
|
54
|
+
|
55
|
+
head = good.first
|
56
|
+
sequences.each { |seq| seq.shift if seq.first == head }
|
57
|
+
result << head
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tail(array)
|
62
|
+
array[1..-1] || []
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Accord
|
2
|
+
UNSPECIFIED = Object.new.tap do |o|
|
3
|
+
o.define_singleton_method(:inspect) { 'UNSPECIFIED' }
|
4
|
+
end
|
5
|
+
|
6
|
+
class SignatureInfo
|
7
|
+
def arguments
|
8
|
+
@arguments ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def param(arg)
|
12
|
+
if arg.is_a?(Symbol) || arg.is_a?(String)
|
13
|
+
arg = { name: arg.to_sym }
|
14
|
+
elsif arg.is_a?(Hash) && arg.size == 1
|
15
|
+
arg = { name: arg.keys.first, default: arg.values.first }
|
16
|
+
else
|
17
|
+
raise ArgumentError, "bad argument: #{arg.inspect}."
|
18
|
+
end
|
19
|
+
arguments << arg
|
20
|
+
end
|
21
|
+
|
22
|
+
def splat(name)
|
23
|
+
arguments << { name: name.to_sym, splat: true }
|
24
|
+
end
|
25
|
+
|
26
|
+
def block(name=nil)
|
27
|
+
if name
|
28
|
+
@block = name.to_sym
|
29
|
+
else
|
30
|
+
@block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?(params)
|
35
|
+
args = normalized_arguments
|
36
|
+
match_without_all_required_args(args, params) ||
|
37
|
+
match_without_all_required_args(args, without_last_defaults(params))
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def normalized_arguments
|
43
|
+
(arguments + (block ? [{ name: block, block: true }] : [])).map do |arg|
|
44
|
+
if arg[:splat]
|
45
|
+
:rest
|
46
|
+
elsif arg[:block]
|
47
|
+
:block
|
48
|
+
elsif arg.has_key?(:default)
|
49
|
+
:opt
|
50
|
+
else
|
51
|
+
:req
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def without_last_defaults(params)
|
57
|
+
params = params.dup
|
58
|
+
params.pop if params.any? && params.last.first == :block
|
59
|
+
params.pop while params.any? && params.last.first == :opt
|
60
|
+
params
|
61
|
+
end
|
62
|
+
|
63
|
+
def match_without_all_required_args(args, params)
|
64
|
+
return false unless args.size == params.size
|
65
|
+
args.each_with_index do |arg, index|
|
66
|
+
if arg == :req && [:req, :opt].include?(params[index].first)
|
67
|
+
next
|
68
|
+
elsif params[index].first == arg
|
69
|
+
next
|
70
|
+
end
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'accord/ro'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
class Specification
|
6
|
+
def initialize(*args)
|
7
|
+
if args.size == 2 || args.first.is_a?(String) || args.first.is_a?(Symbol)
|
8
|
+
@name = args.first.to_s
|
9
|
+
bases = (args.size == 2 ? (args[1] || []) : [])
|
10
|
+
else
|
11
|
+
@name = '?'
|
12
|
+
bases = args.first || []
|
13
|
+
end
|
14
|
+
@dependents = Set.new
|
15
|
+
@ro = Accord::RO.new(self) { |spec| spec.bases }
|
16
|
+
self.bases = bases
|
17
|
+
end
|
18
|
+
|
19
|
+
def bases
|
20
|
+
(@bases || []).dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def bases= new_bases
|
24
|
+
new_bases = new_bases.uniq
|
25
|
+
new_bases.each do |base|
|
26
|
+
raise TypeError,
|
27
|
+
'cannot use something other than Specification as a base' \
|
28
|
+
unless base.is_a?(Specification)
|
29
|
+
end
|
30
|
+
|
31
|
+
bases.each { |base| base.unsubscribe(self) }
|
32
|
+
new_bases.each { |base| base.subscribe(self) }
|
33
|
+
|
34
|
+
@bases = new_bases
|
35
|
+
|
36
|
+
changed(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_interface
|
40
|
+
seen = Set.new
|
41
|
+
@bases.each do |base|
|
42
|
+
base.each_interface do |interface|
|
43
|
+
next if seen.include?(interface)
|
44
|
+
seen << interface
|
45
|
+
yield(interface)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def interfaces
|
51
|
+
enum_for(:each_interface).to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
def ancestors
|
55
|
+
@ro.resolve
|
56
|
+
end
|
57
|
+
|
58
|
+
def extends?(other)
|
59
|
+
ancestors.include?(other)
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
"<Specification #{@name.inspect}>"
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def changed(originally_changed)
|
69
|
+
@ro.changed
|
70
|
+
@dependents.each do |dependent|
|
71
|
+
dependent.changed(originally_changed)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def subscribe(dependent)
|
76
|
+
@dependents << dependent
|
77
|
+
end
|
78
|
+
|
79
|
+
def unsubscribe(dependent)
|
80
|
+
@dependents.delete(dependent)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'accord/interface'
|
2
|
+
require 'accord/base_registry'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
class SubscriberLookup < BaseLookup
|
6
|
+
def all(required, provided)
|
7
|
+
extendor = extendors.get(provided)
|
8
|
+
return [] unless extendor
|
9
|
+
hash.select_expansions(required + [provided, '']) do |key|
|
10
|
+
if required.include?(key)
|
11
|
+
key.ancestors.reverse
|
12
|
+
elsif key.equal?(provided)
|
13
|
+
extendor.current.reverse
|
14
|
+
else
|
15
|
+
['']
|
16
|
+
end
|
17
|
+
end.flatten
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class SubscriptionRegistry < BaseRegistry
|
22
|
+
def self.lookup_class
|
23
|
+
SubscriberLookup
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribe(required, provided, &value)
|
27
|
+
raise ArgumentError, "cannot subscribe without a block" unless value
|
28
|
+
required = normalize_interfaces(required || [nil])
|
29
|
+
provided ||= Interface
|
30
|
+
key = [required, provided, '']
|
31
|
+
(registrations.by_order(required.size)[key] ||= []) << value
|
32
|
+
end
|
33
|
+
|
34
|
+
def unsubscribe(required, provided, value=nil)
|
35
|
+
required = normalize_interfaces(required || [nil])
|
36
|
+
provided ||= Interface
|
37
|
+
lookup = registrations.by_order(required.size)
|
38
|
+
key = [required, provided, '']
|
39
|
+
old = lookup[key] || []
|
40
|
+
|
41
|
+
return if old.empty?
|
42
|
+
|
43
|
+
new = value.nil?? [] : old.select { |v| !v.equal?(value) }
|
44
|
+
if new.any?
|
45
|
+
lookup[key] = new
|
46
|
+
else
|
47
|
+
lookup.delete(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def all(options={})
|
52
|
+
required = normalize_interfaces(options[:required] || [nil])
|
53
|
+
provided = options[:provided] || Interface
|
54
|
+
registrations.by_order(required.size)[[required, provided, '']] || []
|
55
|
+
end
|
56
|
+
|
57
|
+
def lookup(required, provided)
|
58
|
+
required = normalize_interfaces(required || [nil])
|
59
|
+
provided ||= Interface
|
60
|
+
lookup = registrations.by_order(required.size)
|
61
|
+
lookup.all(required, provided)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get(objects, provided=Interface)
|
65
|
+
lookup(map_provided_by(objects), provided).map do |subscriber|
|
66
|
+
subscriber.call(*objects)
|
67
|
+
end.compact
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(objects, provided=Interface)
|
71
|
+
lookup(map_provided_by(objects), provided).each do |subscriber|
|
72
|
+
subscriber.call(*objects)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/accord/tags.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Accord
|
2
|
+
class Tags
|
3
|
+
def initialize
|
4
|
+
@hash = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def []=(tag, value)
|
8
|
+
@hash[tag.to_sym] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](tag)
|
12
|
+
@hash[tag.to_sym]
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch(tag, default=Tags.marker)
|
16
|
+
return @hash[tag] if @hash.has_key?(tag)
|
17
|
+
return default unless default.equal?(self.class.marker)
|
18
|
+
raise ArgumentError, "tag #{tag.inspect} not found."
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.marker
|
22
|
+
@marker ||= Object.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/adapter_registry'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe AdapterRegistry do
|
6
|
+
subject { AdapterRegistry.new }
|
7
|
+
|
8
|
+
let(:adapter_factory) { Proc.new {} }
|
9
|
+
let(:other_adapter_factory) { Proc.new {} }
|
10
|
+
|
11
|
+
|
12
|
+
let(:i1) { stub_interface }
|
13
|
+
let(:i2) { stub_interface }
|
14
|
+
|
15
|
+
def stub_interface(*bases)
|
16
|
+
stub.tap do |interface|
|
17
|
+
interface.stub(
|
18
|
+
:iro => [interface],
|
19
|
+
:ancestors => [interface]
|
20
|
+
)
|
21
|
+
interface.stub(:extends?).and_return(false)
|
22
|
+
interface.stub(:extends?).with(interface).and_return(true)
|
23
|
+
bases.each do |base|
|
24
|
+
interface.iro << base
|
25
|
+
interface.ancestors << base
|
26
|
+
interface.stub(:extends?).with(base).and_return(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#first" do
|
32
|
+
it "returns no adapter when registry is empty" do
|
33
|
+
expect(subject.first).to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
context "registered most simple single adapter" do
|
37
|
+
it "returns that if no arguments are passed" do
|
38
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
39
|
+
expect(subject.first).to be adapter_factory
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "hits right adapter factory if matches" do
|
44
|
+
subject.register([i1], nil, '', &adapter_factory)
|
45
|
+
expect(
|
46
|
+
subject.first(required: [i1], provided: nil, name: '')
|
47
|
+
).to be adapter_factory
|
48
|
+
end
|
49
|
+
|
50
|
+
it "miss adapter factory if none matches" do
|
51
|
+
subject.register([i1], nil, '', &adapter_factory)
|
52
|
+
expect(
|
53
|
+
subject.first(required: [i2], provided: nil, name: '')
|
54
|
+
).to be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#all" do
|
59
|
+
it "returns no adapter when empty" do
|
60
|
+
expect(subject.all).to be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
context "registered most simple single adapter" do
|
64
|
+
before do
|
65
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns that if no arguments are passed" do
|
69
|
+
expect(subject.all).to eq [['', adapter_factory]]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "selection in registration for multiple names" do
|
74
|
+
it "returns all registered adapters" do
|
75
|
+
subject.register([nil], nil, &adapter_factory)
|
76
|
+
subject.register([nil], nil, 'other name', &other_adapter_factory)
|
77
|
+
expect(subject.all(required: [nil], provided: nil)).to eq [
|
78
|
+
['', adapter_factory],
|
79
|
+
['other name', other_adapter_factory]
|
80
|
+
]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#register" do
|
86
|
+
it "complains if registration happens without block" do
|
87
|
+
expect {
|
88
|
+
subject.register([Interface], Interface, '')
|
89
|
+
}.to raise_error(ArgumentError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "allows registering a null adapter" do
|
93
|
+
subject.register([], i1, '', &adapter_factory)
|
94
|
+
expect(
|
95
|
+
subject.first(required: [], provided: i1, name:'')
|
96
|
+
).to be adapter_factory
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when using [nil] and nil for required and provided" do
|
100
|
+
it "defaults to most simple single adapter" do
|
101
|
+
subject.register([nil], nil, '', &adapter_factory)
|
102
|
+
expect(
|
103
|
+
subject.first(required: [Interface], provided: Interface, name: '')
|
104
|
+
).to be adapter_factory
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when using nil and nil for required and provided" do
|
109
|
+
it "defaults to most simple single adapter" do
|
110
|
+
subject.register(nil, nil, '', &adapter_factory)
|
111
|
+
expect(
|
112
|
+
subject.first(required: [Interface], provided: Interface, name: '')
|
113
|
+
).to be adapter_factory
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "registration without name" do
|
118
|
+
it "defaults to empty name" do
|
119
|
+
subject.register([nil], nil, &adapter_factory)
|
120
|
+
expect(
|
121
|
+
subject.first(required: [nil], provided: nil, name: '')
|
122
|
+
).to be adapter_factory
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#lookup" do
|
128
|
+
it "returns no adapter when registry is empty" do
|
129
|
+
expect(subject.lookup([], nil)).to be_nil
|
130
|
+
end
|
131
|
+
|
132
|
+
context "registered most simple single adapter" do
|
133
|
+
before do
|
134
|
+
subject.register([Interface], Interface,
|
135
|
+
'', &adapter_factory)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returns that if required is nil and provided is nil" do
|
139
|
+
expect(subject.lookup(nil, nil)).to be adapter_factory
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns that if required is [nil] and provided is nil" do
|
143
|
+
expect(subject.lookup([nil], nil)).to be adapter_factory
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "hits right adapter factory if matches exactly" do
|
148
|
+
subject.register([i1], nil, '', &adapter_factory)
|
149
|
+
expect(
|
150
|
+
subject.lookup([i1], nil, '')
|
151
|
+
).to be adapter_factory
|
152
|
+
end
|
153
|
+
|
154
|
+
it "defaults name to empty string" do
|
155
|
+
subject.register([i1], nil, '', &adapter_factory)
|
156
|
+
expect(subject.lookup([i1], nil)).to be adapter_factory
|
157
|
+
end
|
158
|
+
|
159
|
+
it "hits right adapter factory if required extends some registered" do
|
160
|
+
i12 = stub_interface(i1)
|
161
|
+
subject.register([i1], nil, '', &adapter_factory)
|
162
|
+
expect(subject.lookup([i12], nil, '')).to be adapter_factory
|
163
|
+
end
|
164
|
+
|
165
|
+
it "miss adapter factory if none matches" do
|
166
|
+
subject.register([i1], nil, '', &adapter_factory)
|
167
|
+
expect(subject.lookup([i2], nil, '')).to be_nil
|
168
|
+
end
|
169
|
+
|
170
|
+
it "returns default value if missed" do
|
171
|
+
subject.register([i1], nil, '', &adapter_factory)
|
172
|
+
expect(subject.lookup([i2], nil, default: 'default')).to eq 'default'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "#lookup_all" do
|
177
|
+
it "returns no adapter when registry is empty" do
|
178
|
+
expect(subject.lookup_all([], nil)).to be_empty
|
179
|
+
end
|
180
|
+
|
181
|
+
context "registered most simple single adapter" do
|
182
|
+
before do
|
183
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "returns that if required is nil and provided is nil" do
|
187
|
+
expect(subject.lookup_all(nil, nil)).to eq({'' => adapter_factory})
|
188
|
+
end
|
189
|
+
|
190
|
+
it "returns that if required is [nil] and provided is nil" do
|
191
|
+
expect(subject.lookup_all([nil], nil)).to eq({'' => adapter_factory})
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it "hits right adapter factory if matches" do
|
196
|
+
subject.register([i1], nil, '', &adapter_factory)
|
197
|
+
expect(subject.lookup_all([i1], nil)).to eq({'' => adapter_factory})
|
198
|
+
end
|
199
|
+
|
200
|
+
it "hits right adapter factory if required extends some registered" do
|
201
|
+
i12 = stub_interface(i1)
|
202
|
+
subject.register([i1], nil, '', &adapter_factory)
|
203
|
+
expect(subject.lookup_all([i12], nil)).to eq({'' => adapter_factory})
|
204
|
+
end
|
205
|
+
|
206
|
+
it "hits right adapter factory if provided is extended by some registered" do
|
207
|
+
i12 = stub_interface(i1)
|
208
|
+
subject.register([nil], i12, '', &adapter_factory)
|
209
|
+
expect(subject.lookup_all([nil], i1)).to eq({'' => adapter_factory})
|
210
|
+
end
|
211
|
+
|
212
|
+
it "in the face of ambiguity, brings the most specific for required" do
|
213
|
+
i12 = stub_interface(i1)
|
214
|
+
subject.register([i1], nil, '', &other_adapter_factory)
|
215
|
+
subject.register([i12], nil, '', &adapter_factory)
|
216
|
+
expect(subject.lookup_all([i12], nil)).to eq({'' => adapter_factory})
|
217
|
+
end
|
218
|
+
|
219
|
+
it "in the face of ambiguity, brings the most specific for provided" do
|
220
|
+
i12 = stub_interface(i1)
|
221
|
+
subject.register([nil], i1, '', &other_adapter_factory)
|
222
|
+
subject.register([nil], i12, '', &adapter_factory)
|
223
|
+
expect(subject.lookup_all([nil], i12)).to eq({'' => adapter_factory})
|
224
|
+
end
|
225
|
+
|
226
|
+
it "miss adapter factory if none matches" do
|
227
|
+
subject.register([i1], nil, '', &adapter_factory)
|
228
|
+
expect(subject.lookup_all([i2], nil)).to be_empty
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "#unregister" do
|
233
|
+
it "doesn't complain when unregistering empty registry" do
|
234
|
+
expect { subject.unregister([nil], nil, '') }.to_not raise_error
|
235
|
+
end
|
236
|
+
|
237
|
+
it "keeps registered if required doesn't match on unregistration" do
|
238
|
+
subject.register([i1], nil, '', &adapter_factory)
|
239
|
+
subject.unregister([i2], nil, '')
|
240
|
+
expect(
|
241
|
+
subject.first(required: [i1], provided: nil, name: '')
|
242
|
+
).to be adapter_factory
|
243
|
+
end
|
244
|
+
|
245
|
+
it "keeps registered if provided doesn't match on unregistration" do
|
246
|
+
subject.register([nil], i1, '', &adapter_factory)
|
247
|
+
subject.unregister([nil], i2, '')
|
248
|
+
expect(
|
249
|
+
subject.first(required: [nil], provided: i1, name: '')
|
250
|
+
).to be adapter_factory
|
251
|
+
end
|
252
|
+
|
253
|
+
it "keeps registered if name doesn't match on unregistration" do
|
254
|
+
subject.register([i1], nil, '', &adapter_factory)
|
255
|
+
subject.unregister([i1], nil, 'other name')
|
256
|
+
expect(
|
257
|
+
subject.first(required: [i1], provided: nil, name: '')
|
258
|
+
).to be adapter_factory
|
259
|
+
end
|
260
|
+
|
261
|
+
it "keeps registered if value is passed but doesn't match on "\
|
262
|
+
"unregistration" do
|
263
|
+
subject.register([i1], nil, '', &adapter_factory)
|
264
|
+
subject.unregister([i1], nil, '', stub)
|
265
|
+
expect(
|
266
|
+
subject.first(required: [i1], provided: nil, name: '')
|
267
|
+
).to be adapter_factory
|
268
|
+
end
|
269
|
+
|
270
|
+
it "unregisters if value not passed and everything else matches "\
|
271
|
+
"previous registration" do
|
272
|
+
subject.register([i1], nil, '', &adapter_factory)
|
273
|
+
subject.unregister([i1], nil, '')
|
274
|
+
expect(
|
275
|
+
subject.first(required: [i1], provided: nil, name: '')
|
276
|
+
).to be_nil
|
277
|
+
end
|
278
|
+
|
279
|
+
context "when using [nil] and nil for required and provided" do
|
280
|
+
it "defaults to most simple single adapter" do
|
281
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
282
|
+
subject.unregister([nil], nil, '')
|
283
|
+
expect(subject.first).to be_nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context "when using nil and nil for required and provided" do
|
288
|
+
it "defaults to most simple single adapter" do
|
289
|
+
subject.register([Interface], Interface, '', &adapter_factory)
|
290
|
+
subject.unregister(nil, nil, '')
|
291
|
+
expect(subject.first).to be_nil
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|