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
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
accord (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.0.8)
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
method_source (0.8.1)
|
12
|
+
multi_json (1.6.1)
|
13
|
+
pry (0.9.12)
|
14
|
+
coderay (~> 1.0.5)
|
15
|
+
method_source (~> 0.8)
|
16
|
+
slop (~> 3.4)
|
17
|
+
pry-doc (0.4.4)
|
18
|
+
pry (>= 0.9.9.6)
|
19
|
+
yard (~> 0.8.1)
|
20
|
+
rspec (2.12.0)
|
21
|
+
rspec-core (~> 2.12.0)
|
22
|
+
rspec-expectations (~> 2.12.0)
|
23
|
+
rspec-mocks (~> 2.12.0)
|
24
|
+
rspec-core (2.12.2)
|
25
|
+
rspec-expectations (2.12.1)
|
26
|
+
diff-lcs (~> 1.1.3)
|
27
|
+
rspec-mocks (2.12.2)
|
28
|
+
simplecov (0.7.1)
|
29
|
+
multi_json (~> 1.0)
|
30
|
+
simplecov-html (~> 0.7.1)
|
31
|
+
simplecov-html (0.7.1)
|
32
|
+
slop (3.4.3)
|
33
|
+
yard (0.8.4.1)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
accord!
|
40
|
+
pry
|
41
|
+
pry-doc
|
42
|
+
rspec
|
43
|
+
simplecov
|
data/accord.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "accord/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "accord"
|
7
|
+
s.version = Accord::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["rcabralc"]
|
10
|
+
s.email = ["rcabralc@gmail.com"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/accord"
|
12
|
+
s.summary = %q{Contracts and adaptation for Ruby}
|
13
|
+
s.description = %q{Contracts and adaptation for Ruby}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency('rspec')
|
20
|
+
end
|
data/lib/accord.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'accord/interface'
|
2
|
+
require 'accord/base_registry'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
class AdapterLookup < BaseLookup
|
6
|
+
def first(required, provided, name='')
|
7
|
+
extendor = extendors.get(provided)
|
8
|
+
return unless extendor
|
9
|
+
return unless result = hash.detect_expansion(required) { |s| s.ancestors }
|
10
|
+
available = extendor.compact_map { |i| result[i] }
|
11
|
+
(available.detect { |h| h[name] } || {})[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def all(required, provided)
|
15
|
+
extendor = extendors.get(provided)
|
16
|
+
return {} unless extendor
|
17
|
+
hashes = hash.select_expansions(required + [provided]) do |key|
|
18
|
+
next key.ancestors.reverse if required.include?(key)
|
19
|
+
extendor.current.reverse
|
20
|
+
end
|
21
|
+
hashes.inject({}) { |result, h| result.merge(h) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AdapterRegistry < BaseRegistry
|
26
|
+
def self.lookup_class
|
27
|
+
AdapterLookup
|
28
|
+
end
|
29
|
+
|
30
|
+
def register(required, provided, name='', &value)
|
31
|
+
raise ArgumentError, "cannot register without a block" unless value
|
32
|
+
required = normalize_interfaces(required || [nil])
|
33
|
+
provided ||= Interface
|
34
|
+
registrations.by_order(required.size)[[required, provided, name]] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def unregister(required, provided, name, value=nil)
|
38
|
+
required = normalize_interfaces(required || [nil])
|
39
|
+
provided ||= Interface
|
40
|
+
|
41
|
+
lookup = registrations.by_order(required.size)
|
42
|
+
key = [required, provided, name]
|
43
|
+
old = lookup[key]
|
44
|
+
|
45
|
+
return if old.nil?
|
46
|
+
return if !value.nil? && !old.equal?(value)
|
47
|
+
|
48
|
+
lookup.delete(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def first(options={})
|
52
|
+
required = normalize_interfaces(options[:required] || [nil])
|
53
|
+
provided = options[:provided] || Interface
|
54
|
+
name = options[:name] || ''
|
55
|
+
registrations.by_order(required.size)[[required, provided, name]]
|
56
|
+
end
|
57
|
+
|
58
|
+
def all(options={})
|
59
|
+
required = normalize_interfaces(options[:required] || [nil])
|
60
|
+
provided = options[:provided] || Interface
|
61
|
+
registrations.by_order(required.size).partial(required, provided)
|
62
|
+
end
|
63
|
+
|
64
|
+
def lookup(required, provided, *args)
|
65
|
+
required = normalize_interfaces(required || [nil])
|
66
|
+
provided ||= Interface
|
67
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
68
|
+
name = args.last || ''
|
69
|
+
default = options.delete(:default)
|
70
|
+
lookup = registrations.by_order(required.size)
|
71
|
+
lookup.first(required, provided, name) || default
|
72
|
+
end
|
73
|
+
|
74
|
+
def lookup_all(required, provided)
|
75
|
+
required = normalize_interfaces(required || [nil])
|
76
|
+
provided ||= Interface
|
77
|
+
lookup = registrations.by_order(required.size)
|
78
|
+
lookup.all(required, provided)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get(objects, provided, *args)
|
82
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
83
|
+
name = args.last || ''
|
84
|
+
default = options.delete(:default)
|
85
|
+
factory = lookup(map_provided_by(objects), provided, name, options)
|
86
|
+
return (factory.call(*objects) || default) if factory
|
87
|
+
default
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
def install_default_adapter_hook
|
93
|
+
install_adapter_hook(Proc.new { |provided, *objects|
|
94
|
+
default_adapter_registry.get(objects, provided)
|
95
|
+
})
|
96
|
+
end
|
97
|
+
|
98
|
+
def clear_default_adapter_hook
|
99
|
+
@default_adapter_registry = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def default_adapter_registry
|
103
|
+
@default_adapter_registry ||= AdapterRegistry.new
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'accord/declarations'
|
2
|
+
require 'accord/extendor_container'
|
3
|
+
require 'accord/nested_key_hash'
|
4
|
+
|
5
|
+
module Accord
|
6
|
+
class Registrations
|
7
|
+
def initialize(lookup_class)
|
8
|
+
@lookup_class = lookup_class
|
9
|
+
end
|
10
|
+
|
11
|
+
def by_order order
|
12
|
+
hash[order] ||= @lookup_class.new(order, extendors)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def hash
|
18
|
+
@hash ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def extendors
|
22
|
+
@extendors ||= ExtendorContainer.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class BaseLookup
|
27
|
+
attr_reader :order, :extendors, :hash
|
28
|
+
|
29
|
+
def initialize(order, extendors)
|
30
|
+
@order = order
|
31
|
+
@extendors = extendors
|
32
|
+
@hash = NestedKeyHash.new
|
33
|
+
super()
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
required, provided, name = key
|
38
|
+
hash[required + [provided] + [name]]
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=(key, value)
|
42
|
+
required, provided, name = key
|
43
|
+
extendors.add(provided)
|
44
|
+
hash[required + [provided] + [name]] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def partial(required, provided)
|
48
|
+
(hash[required + [provided]] || []).sort_by { |name, value| name }
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete(key)
|
52
|
+
required, provided, name = key
|
53
|
+
extendors.delete(provided)
|
54
|
+
hash.delete(required + [provided] + [name])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class BaseRegistry
|
59
|
+
def map_provided_by(objects)
|
60
|
+
objects.map { |object| Accord::Declarations.provided_by(object) }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def registrations
|
66
|
+
@registrations ||= Registrations.new(self.class.lookup_class)
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_interfaces(interfaces)
|
70
|
+
interfaces.map { |i| i.nil?? Accord::Interface : i }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'accord/specification'
|
2
|
+
|
3
|
+
module Accord
|
4
|
+
module Declarations
|
5
|
+
class Declaration < Specification
|
6
|
+
def extends?(interface)
|
7
|
+
super(interface) && interfaces.include?(interface)
|
8
|
+
end
|
9
|
+
|
10
|
+
def + other
|
11
|
+
Declaration.new(interfaces + other.interfaces)
|
12
|
+
end
|
13
|
+
|
14
|
+
def - other
|
15
|
+
Declaration.new(interfaces.select { |i|
|
16
|
+
!other.interfaces.any? { |j| i.extends?(j) }
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Implements < Declaration
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
def initialize(inherit)
|
25
|
+
@inherit = inherit
|
26
|
+
set_name(inherit)
|
27
|
+
@declared = []
|
28
|
+
super(bases_from_inherit)
|
29
|
+
end
|
30
|
+
|
31
|
+
def declare(*interfaces)
|
32
|
+
@declared.concat(normalized(interfaces)).uniq!
|
33
|
+
new_bases = @declared.dup
|
34
|
+
bases_from_inherit.each do |base|
|
35
|
+
new_bases << base if !@declared.include?(base)
|
36
|
+
end
|
37
|
+
self.bases = new_bases
|
38
|
+
end
|
39
|
+
|
40
|
+
def declare_only(*interfaces)
|
41
|
+
@declared = []
|
42
|
+
self.inherit = nil
|
43
|
+
declare(*interfaces)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inspect
|
47
|
+
"#<Implemented by #{name}>"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_accessor :inherit
|
53
|
+
|
54
|
+
def set_name(inherit)
|
55
|
+
if inherit.respond_to?(:name)
|
56
|
+
if inherit.name.to_s != ''
|
57
|
+
@name = inherit.name
|
58
|
+
else
|
59
|
+
@name = inherit.inspect
|
60
|
+
end
|
61
|
+
else
|
62
|
+
@name = inherit.inspect
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def bases_from_inherit
|
67
|
+
if inherit.is_a?(Module)
|
68
|
+
ancestors = inherit.ancestors
|
69
|
+
ancestors.delete(inherit)
|
70
|
+
ancestors.map { |ancestor| Declarations.implemented_by(ancestor) }
|
71
|
+
else
|
72
|
+
[]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def normalized(args)
|
77
|
+
enum_for(:normalize, args).to_a
|
78
|
+
end
|
79
|
+
|
80
|
+
def normalize(args, &block)
|
81
|
+
if args.is_a?(InterfaceClass) || args.is_a?(Implements)
|
82
|
+
block.call(args)
|
83
|
+
else
|
84
|
+
args.each do |arg|
|
85
|
+
normalize(arg, &block)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
def provided_by(object)
|
93
|
+
implemented_by(object.class) + directly_provided_by(object)
|
94
|
+
end
|
95
|
+
|
96
|
+
def directly_provided_by(object)
|
97
|
+
object.instance_eval do
|
98
|
+
@_accord_provides_ ||= Declaration.new
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def implemented_by(factory)
|
103
|
+
factory.instance_eval do
|
104
|
+
@_accord_implements_ ||= Implements.new(self)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def implements(cls, *interfaces)
|
109
|
+
implemented_by(cls).declare(*interfaces)
|
110
|
+
end
|
111
|
+
|
112
|
+
def implements_only(cls, *interfaces)
|
113
|
+
implemented_by(cls).declare_only(*interfaces)
|
114
|
+
end
|
115
|
+
|
116
|
+
def directly_provides(object, *interfaces)
|
117
|
+
object.instance_eval do
|
118
|
+
@_accord_provides_ = Declaration.new(interfaces)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def also_provides(object, *interfaces)
|
123
|
+
directly_provides(object, directly_provided_by(object), *interfaces)
|
124
|
+
end
|
125
|
+
|
126
|
+
def no_longer_provides(object, interface)
|
127
|
+
directly_provides(object, directly_provided_by(object) - interface)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Accord
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class Invalid < Error; end
|
5
|
+
|
6
|
+
class DoesNotImplement < Invalid
|
7
|
+
def initialize(interface)
|
8
|
+
@interface = interface
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class BrokenImplementation < Invalid
|
13
|
+
def initialize(interface, method_name)
|
14
|
+
@interface = interface
|
15
|
+
@method_name = method_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"signature mismatch for #{@interface.inspect}##{@method_name}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Accord
|
2
|
+
class Extendor
|
3
|
+
def initialize
|
4
|
+
@current = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def add(new)
|
8
|
+
return if @current.include?(new)
|
9
|
+
@current = (
|
10
|
+
@current.select { |i| new.extends?(i) } +
|
11
|
+
[new] +
|
12
|
+
@current.select { |i| !new.extends?(i) }
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(old)
|
17
|
+
@current.delete_if { |i| i.equal?(old) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
@current.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def current
|
25
|
+
@current.dup
|
26
|
+
end
|
27
|
+
|
28
|
+
def compact_map
|
29
|
+
@current.map { |i| yield(i) }.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
def flat_map
|
33
|
+
@current.flat_map { |i| yield(i) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|