rubycube 0.2.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.
- 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
|
+
|