rubycube 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +30 -0
- data/MANIFEST +12 -0
- data/README.md +69 -0
- data/Rakefile +50 -0
- data/certs/djberg96_pub.pem +21 -0
- data/cube.gemspec +23 -0
- data/doc/Bar.html +102 -0
- data/doc/CHANGES.html +159 -0
- data/doc/Foo.html +184 -0
- data/doc/Interface.html +278 -0
- data/doc/Interface/MethodMissing.html +104 -0
- data/doc/Interface/PrivateVisibleMethodMissing.html +102 -0
- data/doc/Interface/PublicVisibleMethodMissing.html +102 -0
- data/doc/MANIFEST.html +114 -0
- data/doc/Module.html +153 -0
- data/doc/MyClass.html +234 -0
- data/doc/MyInterface.html +106 -0
- data/doc/MySubInterface.html +110 -0
- data/doc/Object.html +299 -0
- data/doc/README.html +222 -0
- data/doc/Rakefile.html +148 -0
- data/doc/TC_Interface.html +438 -0
- data/doc/certs/djberg96_pub_pem.html +110 -0
- data/doc/created.rid +13 -0
- data/doc/css/fonts.css +167 -0
- data/doc/css/rdoc.css +590 -0
- data/doc/fonts/Lato-Light.ttf +0 -0
- data/doc/fonts/Lato-LightItalic.ttf +0 -0
- data/doc/fonts/Lato-Regular.ttf +0 -0
- data/doc/fonts/Lato-RegularItalic.ttf +0 -0
- data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/doc/images/add.png +0 -0
- data/doc/images/arrow_up.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +121 -0
- data/doc/interface_gemspec.html +121 -0
- data/doc/js/darkfish.js +161 -0
- data/doc/js/jquery.js +4 -0
- data/doc/js/navigation.js +142 -0
- data/doc/js/navigation.js.gz +0 -0
- data/doc/js/search.js +109 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/search_index.js.gz +0 -0
- data/doc/js/searcher.js +228 -0
- data/doc/js/searcher.js.gz +0 -0
- data/doc/table_of_contents.html +227 -0
- data/examples/demo.rb +96 -0
- data/lib/cube.rb +2 -0
- data/lib/cube/interfaces.rb +266 -0
- data/lib/cube/traits.rb +75 -0
- data/test/test_interface.rb +111 -0
- metadata +151 -0
Binary file
|
@@ -0,0 +1,227 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta charset="UTF-8">
|
6
|
+
|
7
|
+
<title>Table of Contents - RDoc Documentation</title>
|
8
|
+
|
9
|
+
<script type="text/javascript">
|
10
|
+
var rdoc_rel_prefix = "./";
|
11
|
+
</script>
|
12
|
+
|
13
|
+
<script src="./js/jquery.js"></script>
|
14
|
+
<script src="./js/darkfish.js"></script>
|
15
|
+
|
16
|
+
<link href="./css/fonts.css" rel="stylesheet">
|
17
|
+
<link href="./css/rdoc.css" rel="stylesheet">
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
<body id="top" class="table-of-contents">
|
22
|
+
<main role="main">
|
23
|
+
<h1 class="class">Table of Contents - RDoc Documentation</h1>
|
24
|
+
|
25
|
+
<h2 id="pages">Pages</h2>
|
26
|
+
<ul>
|
27
|
+
<li class="file">
|
28
|
+
<a href="CHANGES.html">CHANGES</a>
|
29
|
+
|
30
|
+
<ul>
|
31
|
+
<li><a href="CHANGES.html#label-1.0.4+-+8-Jan-2016">1.0.4 - 8-Jan-2016</a>
|
32
|
+
<li><a href="CHANGES.html#label-1.0.3+-+12-Oct-2014">1.0.3 - 12-Oct-2014</a>
|
33
|
+
<li><a href="CHANGES.html#label-1.0.2+-+7-Oct-2009">1.0.2 - 7-Oct-2009</a>
|
34
|
+
<li><a href="CHANGES.html#label-1.0.1+-+29-Jul-2009">1.0.1 - 29-Jul-2009</a>
|
35
|
+
<li><a href="CHANGES.html#label-1.0.0+-+5-Jun-2005">1.0.0 - 5-Jun-2005</a>
|
36
|
+
<li><a href="CHANGES.html#label-0.1.0+-+9-May-2004">0.1.0 - 9-May-2004</a>
|
37
|
+
</ul>
|
38
|
+
</li>
|
39
|
+
<li class="file">
|
40
|
+
<a href="MANIFEST.html">MANIFEST</a>
|
41
|
+
</li>
|
42
|
+
<li class="file">
|
43
|
+
<a href="README.html">README</a>
|
44
|
+
|
45
|
+
<ul>
|
46
|
+
<li><a href="README.html#label-Description">Description</a>
|
47
|
+
<li><a href="README.html#label-Installation">Installation</a>
|
48
|
+
<li><a href="README.html#label-Synopsis">Synopsis</a>
|
49
|
+
<li><a href="README.html#label-General+Notes">General Notes</a>
|
50
|
+
<li><a href="README.html#label-Runtime+performance+of+check+methods">Runtime performance of check methods</a>
|
51
|
+
<li><a href="README.html#label-Developer-27s+Notes">Developer's Notes</a>
|
52
|
+
<li><a href="README.html#label-Acknowledgements">Acknowledgements</a>
|
53
|
+
<li><a href="README.html#label-Copyright">Copyright</a>
|
54
|
+
<li><a href="README.html#label-Warranty">Warranty</a>
|
55
|
+
<li><a href="README.html#label-License">License</a>
|
56
|
+
<li><a href="README.html#label-Author">Author</a>
|
57
|
+
</ul>
|
58
|
+
</li>
|
59
|
+
<li class="file">
|
60
|
+
<a href="Rakefile.html">Rakefile</a>
|
61
|
+
</li>
|
62
|
+
<li class="file">
|
63
|
+
<a href="certs/djberg96_pub_pem.html">djberg96_pub.pem</a>
|
64
|
+
</li>
|
65
|
+
<li class="file">
|
66
|
+
<a href="interface_gemspec.html">interface.gemspec</a>
|
67
|
+
</li>
|
68
|
+
|
69
|
+
</ul>
|
70
|
+
|
71
|
+
<h2 id="classes">Classes and Modules</h2>
|
72
|
+
<ul>
|
73
|
+
<li class="class">
|
74
|
+
<a href="Bar.html">Bar</a>
|
75
|
+
</li>
|
76
|
+
<li class="class">
|
77
|
+
<a href="Foo.html">Foo</a>
|
78
|
+
</li>
|
79
|
+
<li class="module">
|
80
|
+
<a href="Interface.html">Interface</a>
|
81
|
+
</li>
|
82
|
+
<li class="class">
|
83
|
+
<a href="Interface/MethodMissing.html">Interface::MethodMissing</a>
|
84
|
+
</li>
|
85
|
+
<li class="class">
|
86
|
+
<a href="Interface/PrivateVisibleMethodMissing.html">Interface::PrivateVisibleMethodMissing</a>
|
87
|
+
</li>
|
88
|
+
<li class="class">
|
89
|
+
<a href="Interface/PublicVisibleMethodMissing.html">Interface::PublicVisibleMethodMissing</a>
|
90
|
+
</li>
|
91
|
+
<li class="class">
|
92
|
+
<a href="Module.html">Module</a>
|
93
|
+
</li>
|
94
|
+
<li class="class">
|
95
|
+
<a href="MyClass.html">MyClass</a>
|
96
|
+
</li>
|
97
|
+
<li class="module">
|
98
|
+
<a href="MyInterface.html">MyInterface</a>
|
99
|
+
</li>
|
100
|
+
<li class="module">
|
101
|
+
<a href="MySubInterface.html">MySubInterface</a>
|
102
|
+
</li>
|
103
|
+
<li class="class">
|
104
|
+
<a href="Object.html">Object</a>
|
105
|
+
</li>
|
106
|
+
<li class="class">
|
107
|
+
<a href="TC_Interface.html">TC_Interface</a>
|
108
|
+
</li>
|
109
|
+
</ul>
|
110
|
+
|
111
|
+
<h2 id="methods">Methods</h2>
|
112
|
+
<ul>
|
113
|
+
|
114
|
+
<li class="method">
|
115
|
+
<a href="TC_Interface.html#method-c-startup">::startup</a>
|
116
|
+
—
|
117
|
+
<span class="container">TC_Interface</span>
|
118
|
+
|
119
|
+
<li class="method">
|
120
|
+
<a href="Foo.html#method-i-bar">#bar</a>
|
121
|
+
—
|
122
|
+
<span class="container">Foo</span>
|
123
|
+
|
124
|
+
<li class="method">
|
125
|
+
<a href="MyClass.html#method-i-bar">#bar</a>
|
126
|
+
—
|
127
|
+
<span class="container">MyClass</span>
|
128
|
+
|
129
|
+
<li class="method">
|
130
|
+
<a href="MyClass.html#method-i-baz">#baz</a>
|
131
|
+
—
|
132
|
+
<span class="container">MyClass</span>
|
133
|
+
|
134
|
+
<li class="method">
|
135
|
+
<a href="Object.html#method-i-check_class">#check_class</a>
|
136
|
+
—
|
137
|
+
<span class="container">Object</span>
|
138
|
+
|
139
|
+
<li class="method">
|
140
|
+
<a href="Object.html#method-i-check_interface">#check_interface</a>
|
141
|
+
—
|
142
|
+
<span class="container">Object</span>
|
143
|
+
|
144
|
+
<li class="method">
|
145
|
+
<a href="TC_Interface.html#method-i-checker_method">#checker_method</a>
|
146
|
+
—
|
147
|
+
<span class="container">TC_Interface</span>
|
148
|
+
|
149
|
+
<li class="method">
|
150
|
+
<a href="Foo.html#method-i-foo">#foo</a>
|
151
|
+
—
|
152
|
+
<span class="container">Foo</span>
|
153
|
+
|
154
|
+
<li class="method">
|
155
|
+
<a href="MyClass.html#method-i-foo">#foo</a>
|
156
|
+
—
|
157
|
+
<span class="container">MyClass</span>
|
158
|
+
|
159
|
+
<li class="method">
|
160
|
+
<a href="Module.html#method-i-implements-3F">#implements?</a>
|
161
|
+
—
|
162
|
+
<span class="container">Module</span>
|
163
|
+
|
164
|
+
<li class="method">
|
165
|
+
<a href="Object.html#method-i-interface">#interface</a>
|
166
|
+
—
|
167
|
+
<span class="container">Object</span>
|
168
|
+
|
169
|
+
<li class="method">
|
170
|
+
<a href="Interface.html#method-i-private_visible">#private_visible</a>
|
171
|
+
—
|
172
|
+
<span class="container">Interface</span>
|
173
|
+
|
174
|
+
<li class="method">
|
175
|
+
<a href="Interface.html#method-i-public_visible">#public_visible</a>
|
176
|
+
—
|
177
|
+
<span class="container">Interface</span>
|
178
|
+
|
179
|
+
<li class="method">
|
180
|
+
<a href="Interface.html#method-i-required_public_methods">#required_public_methods</a>
|
181
|
+
—
|
182
|
+
<span class="container">Interface</span>
|
183
|
+
|
184
|
+
<li class="method">
|
185
|
+
<a href="TC_Interface.html#method-i-test_alpha_interface_requirements_met">#test_alpha_interface_requirements_met</a>
|
186
|
+
—
|
187
|
+
<span class="container">TC_Interface</span>
|
188
|
+
|
189
|
+
<li class="method">
|
190
|
+
<a href="TC_Interface.html#method-i-test_gamma_interface_requirements_met">#test_gamma_interface_requirements_met</a>
|
191
|
+
—
|
192
|
+
<span class="container">TC_Interface</span>
|
193
|
+
|
194
|
+
<li class="method">
|
195
|
+
<a href="TC_Interface.html#method-i-test_interface_requirements_not_met">#test_interface_requirements_not_met</a>
|
196
|
+
—
|
197
|
+
<span class="container">TC_Interface</span>
|
198
|
+
|
199
|
+
<li class="method">
|
200
|
+
<a href="TC_Interface.html#method-i-test_method_check">#test_method_check</a>
|
201
|
+
—
|
202
|
+
<span class="container">TC_Interface</span>
|
203
|
+
|
204
|
+
<li class="method">
|
205
|
+
<a href="TC_Interface.html#method-i-test_sub_interface_requirements_not_met">#test_sub_interface_requirements_not_met</a>
|
206
|
+
—
|
207
|
+
<span class="container">TC_Interface</span>
|
208
|
+
|
209
|
+
<li class="method">
|
210
|
+
<a href="TC_Interface.html#method-i-test_version">#test_version</a>
|
211
|
+
—
|
212
|
+
<span class="container">TC_Interface</span>
|
213
|
+
|
214
|
+
<li class="method">
|
215
|
+
<a href="Interface.html#method-i-unrequired_methods">#unrequired_methods</a>
|
216
|
+
—
|
217
|
+
<span class="container">Interface</span>
|
218
|
+
</ul>
|
219
|
+
</main>
|
220
|
+
|
221
|
+
|
222
|
+
<footer id="validator-badges" role="contentinfo">
|
223
|
+
<p><a href="http://validator.w3.org/check/referer">Validate</a>
|
224
|
+
<p>Generated by <a href="http://docs.seattlerb.org/rdoc/">RDoc</a> 4.2.0.
|
225
|
+
<p>Based on <a href="http://deveiate.org/projects/Darkfish-RDoc/">Darkfish</a> by <a href="http://deveiate.org">Michael Granger</a>.
|
226
|
+
</footer>
|
227
|
+
|
data/examples/demo.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# run as `RUBY_INTERFACE_TYPECHECK= 1 ruby examples/demo.rb`
|
2
|
+
require_relative '../lib/cube'
|
3
|
+
|
4
|
+
Adder = interface {
|
5
|
+
# sum is a method that takes an array of Integer and returns an Integer
|
6
|
+
proto(:sum, [Integer]) { Integer }
|
7
|
+
}
|
8
|
+
|
9
|
+
Calculator = interface {
|
10
|
+
# interfaces can be composed
|
11
|
+
extends Adder
|
12
|
+
# method fact takes an Integer and returns an Integer
|
13
|
+
proto(:fact, Integer) { Integer }
|
14
|
+
# method pos takes an array of Integers, an Integer, and returns either Integer or nil
|
15
|
+
proto(:pos, [Integer], Integer) { [Integer, NilClass].to_set }
|
16
|
+
}
|
17
|
+
|
18
|
+
class SimpleCalc
|
19
|
+
def fact(n)
|
20
|
+
(2..n).reduce(1) { |m, e| m * e }
|
21
|
+
end
|
22
|
+
|
23
|
+
def sum(a)
|
24
|
+
a.reduce(0, &:+)
|
25
|
+
end
|
26
|
+
|
27
|
+
def pos(arr, i)
|
28
|
+
arr.index(i)
|
29
|
+
end
|
30
|
+
|
31
|
+
# implements Calculator #, runtime_checks: false # default is true
|
32
|
+
end
|
33
|
+
|
34
|
+
c = SimpleCalc.as_interface(Calculator).new
|
35
|
+
# If SimpleCalc does not have `implements Calculator`, but its methods match the interface
|
36
|
+
# you can "cast" it to Calculator - `SimpleCalc.as_interface(Calculator).new`
|
37
|
+
# This is useful for casting classes that you did not write
|
38
|
+
p c.sum([1, 2])
|
39
|
+
p c.pos([1, 2, 3], 4)
|
40
|
+
|
41
|
+
AdvancedCalculator = interface {
|
42
|
+
extend Calculator
|
43
|
+
proto(:product, Integer, Integer) { Integer }
|
44
|
+
}
|
45
|
+
|
46
|
+
module AdvancedCalcT
|
47
|
+
extend Cube::Trait
|
48
|
+
def product(a, b)
|
49
|
+
ret = 0
|
50
|
+
a.times { ret = sum([ret, b]) }
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
|
54
|
+
def sum; end # A class method always takes precedence, no conflict here
|
55
|
+
def foo; end # this conflicts with DummyCalcT. Needs to be aliased (see below)
|
56
|
+
def bar; end # this conflicts with DummyCalcT. Needs to be aliased (see below)
|
57
|
+
|
58
|
+
requires_interface Adder # Note that this will give an error if SimpleCalc#sum is removed
|
59
|
+
# even if this trait itself has a `sum` method
|
60
|
+
end
|
61
|
+
|
62
|
+
module DummyCalcT
|
63
|
+
extend Cube::Trait
|
64
|
+
def sum; end # this method conflicts with AdvancedCalcT, but SimpleCalc#sum takes precedence
|
65
|
+
def foo; end
|
66
|
+
def bar; end
|
67
|
+
class << self
|
68
|
+
def included(_)
|
69
|
+
$stderr.puts "Works like a regular module as well"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# This is how we compose behaviours
|
75
|
+
# AdvancedCalc is a class which mixes traits AdvancedCalcT and DummyCalcT
|
76
|
+
# into SimpleCalc and implements the interface AdvancedCalculator
|
77
|
+
# To avoid conflicts, alias methods in AdvancedCalcT (otherwise error will be raised)
|
78
|
+
# One can also suppress methods in DummyCalcT
|
79
|
+
AdvancedCalc = SimpleCalc.with_trait(AdvancedCalcT,
|
80
|
+
aliases: { foo: :adfoo, bar: :adbar })
|
81
|
+
.with_trait(DummyCalcT, suppresses: [:foo, :bar])
|
82
|
+
.as_interface(AdvancedCalculator)
|
83
|
+
|
84
|
+
sc = AdvancedCalc.new
|
85
|
+
p sc.product(3, 2)
|
86
|
+
|
87
|
+
|
88
|
+
# Benchmarks. Run with RUBY_INTERFACE_TYPECHECK=0 and 1 to compare
|
89
|
+
|
90
|
+
t1 = Time.now
|
91
|
+
1_000_000.times do
|
92
|
+
c.fact(50)
|
93
|
+
end
|
94
|
+
t2 = Time.now
|
95
|
+
|
96
|
+
p t2 - t1
|
data/lib/cube.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'set'
|
3
|
+
# A module for implementing Java style interfaces in Ruby. For more information
|
4
|
+
# about Java interfaces, please see:
|
5
|
+
#
|
6
|
+
# http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
|
7
|
+
#
|
8
|
+
module Cube
|
9
|
+
module Interface
|
10
|
+
# The version of the interface library.
|
11
|
+
Interface::VERSION = '0.2.0'
|
12
|
+
|
13
|
+
# Raised if a class or instance does not meet the interface requirements.
|
14
|
+
class MethodMissing < RuntimeError; end
|
15
|
+
class PrivateVisibleMethodMissing < MethodMissing; end
|
16
|
+
class PublicVisibleMethodMissing < MethodMissing; end
|
17
|
+
class MethodArityError < RuntimeError; end
|
18
|
+
class TypeMismatchError < RuntimeError; end
|
19
|
+
|
20
|
+
alias :extends :extend
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def convert_to_lambda &block
|
25
|
+
obj = Object.new
|
26
|
+
obj.define_singleton_method(:_, &block)
|
27
|
+
return obj.method(:_).to_proc
|
28
|
+
end
|
29
|
+
|
30
|
+
def extend_object(obj)
|
31
|
+
return append_features(obj) if Interface === obj
|
32
|
+
append_features(class << obj; self end)
|
33
|
+
included(obj)
|
34
|
+
end
|
35
|
+
|
36
|
+
def append_features(mod)
|
37
|
+
return super if Interface === mod
|
38
|
+
|
39
|
+
# Is this a sub-interface?
|
40
|
+
inherited = (self.ancestors-[self]).select{ |x| Interface === x }
|
41
|
+
inherited_ids = inherited.map{ |x| x.instance_variable_get('@ids') }
|
42
|
+
|
43
|
+
# Store required method ids
|
44
|
+
inherited_specs = map_spec(inherited_ids.flatten)
|
45
|
+
specs = @ids.merge(inherited_specs)
|
46
|
+
ids = @ids.keys + map_spec(inherited_ids.flatten).keys
|
47
|
+
@unreq ||= []
|
48
|
+
|
49
|
+
|
50
|
+
# Iterate over the methods, minus the unrequired methods, and raise
|
51
|
+
# an error if the method has not been defined.
|
52
|
+
mod_public_instance_methods = mod.public_instance_methods(true)
|
53
|
+
(ids - @unreq).uniq.each do |id|
|
54
|
+
id = id.to_s if RUBY_VERSION.to_f < 1.9
|
55
|
+
unless mod_public_instance_methods.include?(id)
|
56
|
+
raise Interface::PublicVisibleMethodMissing, "#{mod}: #{self}##{id}"
|
57
|
+
end
|
58
|
+
spec = specs[id]
|
59
|
+
if spec.is_a?(Hash) && spec.key?(:in) && spec[:in].is_a?(Array)
|
60
|
+
replace_check_method(mod, id, spec[:in], spec[:out])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
inherited_private_ids = inherited.map{ |x| x.instance_variable_get('@private_ids') }
|
65
|
+
# Store required method ids
|
66
|
+
private_ids = @private_ids.keys + map_spec(inherited_private_ids.flatten).keys
|
67
|
+
|
68
|
+
# Iterate over the methods, minus the unrequired methods, and raise
|
69
|
+
# an error if the method has not been defined.
|
70
|
+
mod_all_methods = mod.instance_methods(true) + mod.private_instance_methods(true)
|
71
|
+
|
72
|
+
(private_ids - @unreq).uniq.each do |id|
|
73
|
+
id = id.to_s if RUBY_VERSION.to_f < 1.9
|
74
|
+
unless mod_all_methods.include?(id)
|
75
|
+
raise Interface::PrivateVisibleMethodMissing, "#{mod}: #{self}##{id}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
super mod
|
80
|
+
end
|
81
|
+
|
82
|
+
def replace_check_method(mod, id, inchecks, outcheck)
|
83
|
+
orig_method = mod.instance_method(id)
|
84
|
+
|
85
|
+
unless mod.instance_variable_defined?("@__interface_arity_skip") \
|
86
|
+
&& mod.instance_variable_get("@__interface_arity_skip")
|
87
|
+
orig_arity = orig_method.parameters.size
|
88
|
+
check_arity = inchecks.size
|
89
|
+
if orig_arity != check_arity
|
90
|
+
raise Interface::MethodArityError,
|
91
|
+
"#{mod}: #{self}##{id} arity mismatch: #{orig_arity} instead of #{check_arity}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
unless ENV['RUBY_CUBE_TYPECHECK'].to_i > 0 \
|
96
|
+
&& mod.instance_variable_defined?("@__interface_runtime_check") \
|
97
|
+
&& mod.instance_variable_get("@__interface_runtime_check")
|
98
|
+
return
|
99
|
+
end
|
100
|
+
iface = self
|
101
|
+
mod.class_exec do
|
102
|
+
ns_meth_name = "#{id}_#{SecureRandom.hex(3)}".to_sym
|
103
|
+
alias_method ns_meth_name, id
|
104
|
+
define_method(id) do |*args|
|
105
|
+
args.each_index do |i|
|
106
|
+
v, t = args[i], inchecks[i]
|
107
|
+
begin
|
108
|
+
check_type(t, v)
|
109
|
+
rescue Interface::TypeMismatchError => e
|
110
|
+
raise Interface::TypeMismatchError,
|
111
|
+
"#{mod}: #{iface}##{id} (arg: #{i}): #{e.message}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
ret = send(ns_meth_name, *args)
|
115
|
+
begin
|
116
|
+
check_type(outcheck, ret) if outcheck
|
117
|
+
rescue Interface::TypeMismatchError => e
|
118
|
+
raise Interface::TypeMismatchError,
|
119
|
+
"#{mod}: #{iface}##{id} (return): #{e.message}"
|
120
|
+
end
|
121
|
+
ret
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# def verify_arity(mod, meth)
|
127
|
+
# arity = mod.instance_method(meth).arity
|
128
|
+
# unless arity == @ids[meth]
|
129
|
+
# raise Interface::MethodArityError, "#{mod}: #{self}##{meth}=#{arity}. Should be #{@ids[meth]}"
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
|
133
|
+
def map_spec(ids)
|
134
|
+
ids.reduce({}) do |res, m|
|
135
|
+
if m.is_a?(Hash)
|
136
|
+
res.merge(m)
|
137
|
+
elsif m.is_a?(Symbol) || m.is_a?(String)
|
138
|
+
res.merge({ m.to_sym => nil })
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def validate_spec(spec)
|
144
|
+
[*spec].each do |t|
|
145
|
+
if t.is_a?(Array)
|
146
|
+
unless t.first.is_a?(Module)
|
147
|
+
raise ArgumentError, "#{t} does not contain a Module or Interface"
|
148
|
+
end
|
149
|
+
elsif !t.is_a?(Module)
|
150
|
+
raise ArgumentError, "#{t} is not a Module or Interface"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
public
|
156
|
+
# Accepts an array of method names that define the interface. When this
|
157
|
+
# module is included/implemented, those method names must have already been
|
158
|
+
# defined.
|
159
|
+
#
|
160
|
+
def required_public_methods
|
161
|
+
@ids.keys
|
162
|
+
end
|
163
|
+
|
164
|
+
def proto(meth, *args)
|
165
|
+
out_spec = yield if block_given?
|
166
|
+
validate_spec(args)
|
167
|
+
validate_spec(out_spec) if out_spec
|
168
|
+
@ids.merge!({ meth.to_sym => { in: args, out: out_spec }})
|
169
|
+
end
|
170
|
+
|
171
|
+
def public_visible(*ids)
|
172
|
+
unless ids.all? { |id| id.is_a?(Symbol) || id.is_a?(String) }
|
173
|
+
raise ArgumentError, "Arguments should be strings or symbols"
|
174
|
+
end
|
175
|
+
spec = map_spec(ids)
|
176
|
+
@ids.merge!(spec)
|
177
|
+
end
|
178
|
+
|
179
|
+
def private_visible(*ids)
|
180
|
+
unless ids.all? { |id| id.is_a?(Symbol) || id.is_a?(String) }
|
181
|
+
raise ArgumentError, "Arguments should be strings or symbols"
|
182
|
+
end
|
183
|
+
spec = map_spec(ids)
|
184
|
+
@private_ids.merge!(spec)
|
185
|
+
end
|
186
|
+
# Accepts an array of method names that are removed as a requirement for
|
187
|
+
# implementation. Presumably you would use this in a sub-interface where
|
188
|
+
# you only wanted a partial implementation of an existing interface.
|
189
|
+
#
|
190
|
+
def unrequired_methods(*ids)
|
191
|
+
@unreq ||= []
|
192
|
+
@unreq += ids
|
193
|
+
end
|
194
|
+
|
195
|
+
def shell
|
196
|
+
ids = @ids
|
197
|
+
unreq = @unreq
|
198
|
+
cls = Class.new(Object) do
|
199
|
+
(ids.keys - unreq).each do |m|
|
200
|
+
define_method(m) { |*args| }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
cls.send(:shell_implements, self)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
class Object
|
209
|
+
def interface(&block)
|
210
|
+
mod = Module.new
|
211
|
+
mod.extend(Cube::Interface)
|
212
|
+
mod.instance_variable_set('@ids', {})
|
213
|
+
mod.instance_variable_set('@private_ids', {})
|
214
|
+
mod.instance_eval(&block)
|
215
|
+
mod
|
216
|
+
end
|
217
|
+
|
218
|
+
if ENV['RUBY_CUBE_TYPECHECK'].to_i > 0
|
219
|
+
def check_type(t, v)
|
220
|
+
if t.is_a?(Set)
|
221
|
+
unless t.any? { |tp| check_type(tp, v) rescue false }
|
222
|
+
raise Cube::Interface::TypeMismatchError,
|
223
|
+
"#{v.inspect} is not any of #{tp.to_a}" unless v.is_a?(tp)
|
224
|
+
end
|
225
|
+
return
|
226
|
+
end
|
227
|
+
if t.is_a? Array
|
228
|
+
raise Cube::Interface::TypeMismatchError,
|
229
|
+
"#{v} is not an Array" unless v.is_a? Array
|
230
|
+
check_type(t.first, v.first)
|
231
|
+
check_type(t.first, v.last)
|
232
|
+
return
|
233
|
+
end
|
234
|
+
raise Cube::Interface::TypeMismatchError, "#{v.inspect} is not type #{t}" unless v.is_a? t
|
235
|
+
true
|
236
|
+
end
|
237
|
+
else
|
238
|
+
def check_type(*_); end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
class Module
|
243
|
+
def implements(mod, runtime_checks: true)
|
244
|
+
unless is_a? Class
|
245
|
+
raise "Non-Class modules should not implement interfaces"
|
246
|
+
end
|
247
|
+
instance_variable_set(:@__interface_runtime_check, true) if runtime_checks
|
248
|
+
include(mod)
|
249
|
+
end
|
250
|
+
|
251
|
+
def as_interface(iface, runtime_checks: true)
|
252
|
+
clone.implements(iface, runtime_checks: runtime_checks)
|
253
|
+
end
|
254
|
+
|
255
|
+
def assert_implements(iface)
|
256
|
+
clone.implements(iface, false)
|
257
|
+
end
|
258
|
+
|
259
|
+
def shell_implements(mod)
|
260
|
+
instance_variable_set(:@__interface_runtime_check, false)
|
261
|
+
instance_variable_set(:@__interface_arity_skip, true)
|
262
|
+
include(mod)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
|