accord 0.1.0
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.
- 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
|