appkernel 0.1.3 → 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.
- data/History.txt +11 -2
- data/Manifest.txt +4 -2
- data/appkernel.gemspec +3 -3
- data/lib/appkernel.rb +3 -2
- data/lib/appkernel/curry.rb +27 -0
- data/lib/appkernel/function.rb +206 -206
- data/lib/appkernel/types.rb +25 -0
- data/spec/appkernel/curry_spec.rb +75 -0
- data/spec/appkernel/function_spec.rb +220 -213
- data/spec/appkernel/types_spec.rb +55 -0
- metadata +6 -4
- data/lib/appkernel/validation.rb +0 -82
- data/spec/appkernel/validation_spec.rb +0 -32
@@ -0,0 +1,25 @@
|
|
1
|
+
class Integer
|
2
|
+
def self.to_option(spec)
|
3
|
+
spec.to_i
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Float
|
8
|
+
def self.to_option(spec)
|
9
|
+
spec.to_f
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class AppKernel
|
14
|
+
|
15
|
+
Boolean = [TrueClass, FalseClass]
|
16
|
+
|
17
|
+
def Boolean.to_option(o)
|
18
|
+
case o.to_s.downcase.strip
|
19
|
+
when "true", "t", "on", "y", "yes"
|
20
|
+
true
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe "Function Currying " do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@mult = Class.new(AppKernel::Function).class_eval do
|
7
|
+
self.tap do
|
8
|
+
option :lhs, :index => 1, :type => Numeric, :required => true
|
9
|
+
option :rhs, :index => 2, :type => Numeric, :required => true
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@lhs * @rhs
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
it "new functions to be created by fixing values of specified options" do
|
21
|
+
@mult.curry(2).tap do |double|
|
22
|
+
double.call(3).should == 6
|
23
|
+
double.call(:rhs => 10).should == 20
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
it "is an error to curry options that do not exists" do
|
29
|
+
lambda {
|
30
|
+
@mult.curry(:hello => "world")
|
31
|
+
}.should raise_error(AppKernel::OptionsError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "the curried options will not be overriden and in fact will raise an error if an attempt is made to do so" do
|
35
|
+
lambda {
|
36
|
+
@mult.curry(:lhs => 2).call(:lhs => 4, :rhs => 5)
|
37
|
+
}.should raise_error(AppKernel::OptionsError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "requires all curried options to go through resolution and type matching" do
|
41
|
+
Class.new(AppKernel::Function).class_eval do
|
42
|
+
option :int, :type => Integer
|
43
|
+
option :float, :type => Float
|
44
|
+
|
45
|
+
def execute
|
46
|
+
@float + @int
|
47
|
+
end
|
48
|
+
|
49
|
+
self.curry(:float => "3.14").call(:int => 5).should == 8.14
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is an error to have a nil value for a curried option if that option is required" do
|
54
|
+
lambda {
|
55
|
+
@mult.curry(:lhs => nil)
|
56
|
+
}.should raise_error(AppKernel::OptionsError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "automatically nils out an option if it is curried with nil" do
|
60
|
+
Class.new(AppKernel::Function).class_eval do
|
61
|
+
option :one
|
62
|
+
option :two
|
63
|
+
|
64
|
+
def execute
|
65
|
+
[@one, @two]
|
66
|
+
end
|
67
|
+
|
68
|
+
curry(:one => nil).call(:two => 2).should == [nil,2]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "allows functions to be curried multiple times" do
|
73
|
+
@mult.curry(:lhs => 2).curry(:rhs => 3).call.should == 6
|
74
|
+
end
|
75
|
+
end
|
@@ -1,271 +1,278 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
-
|
2
|
+
|
3
3
|
describe AppKernel::Function do
|
4
|
-
|
4
|
+
|
5
|
+
# Called before each example.
|
5
6
|
before(:each) do
|
6
|
-
@
|
7
|
-
mod.module_eval do
|
8
|
-
include AppKernel::Function
|
9
|
-
end
|
10
|
-
end
|
11
|
-
extend @mod
|
12
|
-
@klass = Class.new.class_eval do
|
13
|
-
include AppKernel::Function; self
|
14
|
-
end
|
7
|
+
@function = Class.new(AppKernel::Function)
|
15
8
|
end
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
|
9
|
+
|
10
|
+
def class_eval(&block)
|
11
|
+
@function.class_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
describe "Calling Conventions" do
|
16
|
+
it "allows modules to define an function functions that takes options" do
|
17
|
+
class_eval do
|
20
18
|
option :word
|
21
|
-
|
22
|
-
execute
|
19
|
+
|
20
|
+
def execute
|
23
21
|
@word
|
24
22
|
end
|
25
23
|
end
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
it "allows certain options to be the default option" do
|
31
|
-
function :Say do
|
32
|
-
option :greeting, :index => 0
|
33
|
-
option :to, :index => 1
|
34
|
-
option :extra
|
35
|
-
|
36
|
-
execute do
|
37
|
-
"#{@greeting} #{@to}#{(', ' + @extra) if @extra}"
|
38
|
-
end
|
24
|
+
|
25
|
+
@function.call(:word => "hello").should == "hello"
|
39
26
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
execute
|
49
|
-
"
|
27
|
+
|
28
|
+
it "allows certain options to be the default option" do
|
29
|
+
|
30
|
+
class_eval do
|
31
|
+
option :greeting, :index => 0
|
32
|
+
option :to, :index => 1
|
33
|
+
option :extra
|
34
|
+
|
35
|
+
def execute
|
36
|
+
"#{@greeting} #{@to}#{(', ' + @extra) if @extra}"
|
50
37
|
end
|
51
38
|
end
|
39
|
+
@function.call("Hello", "Charles", :extra => "How are you?").should == "Hello Charles, How are you?"
|
40
|
+
@function.call("Hello", "Charles").should == "Hello Charles"
|
41
|
+
@function.call(:greeting => "Hello", :to => "Charles").should == "Hello Charles"
|
52
42
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
it "allows classes to define functions as well as modules" do
|
63
|
-
@klass.class_eval do
|
64
|
-
function :Say do
|
65
|
-
option :word, :index => 1
|
66
|
-
execute do
|
67
|
-
@word
|
43
|
+
|
44
|
+
|
45
|
+
it "can be called by using the apply method instead of invoking it directly" do
|
46
|
+
class_eval do
|
47
|
+
option :desc, :index => 1
|
48
|
+
|
49
|
+
def execute
|
50
|
+
"FiveAlive is a #{@desc}"
|
68
51
|
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
it "allows functions in the same module to be accessible without namespacing" do
|
74
|
-
function :First do
|
75
|
-
execute do
|
76
|
-
Second()
|
77
52
|
end
|
53
|
+
result = @function.apply("candy bar")
|
54
|
+
result.return_value.should == "FiveAlive is a candy bar"
|
55
|
+
result.successful?.should be(true)
|
78
56
|
end
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
57
|
+
|
58
|
+
it "can have required options, but they are never required by default" do
|
59
|
+
class_eval do
|
60
|
+
option :greeting, :index => 1, :required => true
|
61
|
+
option :to, :index => 2, :required => true
|
62
|
+
option :extra, :index => 3
|
63
|
+
end
|
64
|
+
|
65
|
+
@function.apply("Hello", "World", "I'm doing fine.").tap do |result|
|
66
|
+
result.successful?.should be(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
@function.apply.tap do |result|
|
70
|
+
result.return_value.should be(nil)
|
71
|
+
result.should_not be_successful
|
72
|
+
result.errors[:greeting].should_not be_empty
|
73
|
+
result.errors[:to].should_not be_empty
|
74
|
+
result.errors[:extra].should be_empty
|
75
|
+
|
76
|
+
result.errors.length.should be(2)
|
83
77
|
end
|
84
78
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
it "can be called by using the apply method instead of invoking it directly" do
|
90
|
-
function :FiveAlive do
|
91
|
-
option :desc, :index => 1
|
92
|
-
execute do
|
93
|
-
"FiveAlive is a #{@desc}"
|
79
|
+
|
80
|
+
it "raises an error immediately if you try to call a function that has invalid arguments" do
|
81
|
+
class_eval do
|
82
|
+
option :mandatory, :required => true
|
94
83
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
it "can have required options, but they are never required by default" do
|
102
|
-
function :Say do
|
103
|
-
option :greeting, :index => 1, :required => true
|
104
|
-
option :to, :index => 2, :required => true
|
105
|
-
option :extra, :index => 3
|
106
|
-
end
|
107
|
-
result = apply(@mod::Say, "Hello", "World", "I'm doing fine.")
|
108
|
-
result.successful?.should be(true)
|
109
|
-
result = apply(@mod::Say)
|
110
|
-
result.return_value.should be(nil)
|
111
|
-
result.successful?.should be(false)
|
112
|
-
result.errors[:greeting].should_not be(nil)
|
113
|
-
result.errors[:to].should_not be(nil)
|
114
|
-
result.errors[:extra].should be(nil)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "raises an error immediately if you try to call a function that has invalid arguments" do
|
118
|
-
function :Harpo do
|
119
|
-
option :mandatory, :required => true
|
84
|
+
|
85
|
+
lambda {
|
86
|
+
@function.call()
|
87
|
+
}.should raise_error(AppKernel::OptionsError)
|
120
88
|
end
|
121
89
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
result = apply(@mod::Picky, 7)
|
141
|
-
result.successful?.should be(false)
|
142
|
-
result.errors[:arg].should == "must be 5 and not be 6"
|
90
|
+
it "allows validation of its arguments" do
|
91
|
+
class_eval do
|
92
|
+
option :arg, :index => 1
|
93
|
+
|
94
|
+
def validate(this)
|
95
|
+
this.check(@arg == 5 && @arg != 6, "'arg' must be 5 and not be 6")
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
@function.apply(5).should be_successful
|
101
|
+
@function.apply(6).tap do |result|
|
102
|
+
result.should_not be_successful
|
103
|
+
result.errors.to_a.should == ["'arg' must be 5 and not be 6"]
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
143
107
|
end
|
144
|
-
|
108
|
+
|
145
109
|
describe "Option Resolution" do
|
146
|
-
it "can take a
|
147
|
-
|
148
|
-
option :num, :index => 1, :
|
149
|
-
|
150
|
-
execute
|
110
|
+
it "can take a lookup parameter in the function definition which tells it how to lookup arguments" do
|
111
|
+
class_eval do
|
112
|
+
option :num, :index => 1, :lookup => proc {|s| s.to_i}
|
113
|
+
|
114
|
+
def execute
|
151
115
|
@num
|
152
116
|
end
|
153
117
|
end
|
154
|
-
|
155
|
-
|
118
|
+
|
119
|
+
@function.call("5").should == 5
|
156
120
|
end
|
157
121
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
execute{@value}
|
164
|
-
end
|
122
|
+
it "has a :parse options which are aliases for :lookup" do
|
123
|
+
class_eval do
|
124
|
+
option :num1, :index => 1, :lookup => proc {|s| s.to_i}
|
125
|
+
option :num2, :index => 2, :parse => proc {|s| s.to_i}
|
165
126
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
it "requires that the default value be the same as the option type if that is specified" do
|
170
|
-
lambda {
|
171
|
-
function :InvalidDefault do
|
172
|
-
option :value, :type => Integer, :default => "NOT_INT"
|
173
|
-
end
|
174
|
-
}.should raise_error
|
127
|
+
def execute
|
128
|
+
[@num1,@num2]
|
129
|
+
end
|
175
130
|
end
|
176
131
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
132
|
+
@function.call("1", "2").should == [1,2]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "has default parsers/lookups if the type is specified" do
|
136
|
+
class_eval do
|
137
|
+
option :num, :type => Integer
|
138
|
+
option :float, :type => Float
|
139
|
+
|
140
|
+
def execute
|
141
|
+
OpenStruct.new({
|
142
|
+
:num => @num,
|
143
|
+
:float => @float
|
144
|
+
})
|
181
145
|
end
|
182
146
|
end
|
183
147
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
execute{@value}
|
188
|
-
end
|
189
|
-
|
190
|
-
ExplicitNil(:value => nil).should == 'fun'
|
148
|
+
@function.call(:num => "5", :float => "3.14").tap do |result|
|
149
|
+
result.num.should == 5
|
150
|
+
result.float.should == 3.14
|
191
151
|
end
|
192
152
|
end
|
193
153
|
|
194
154
|
it "doesn't do an argument conversion if the argument is already of the correct type" do
|
195
|
-
|
196
|
-
option :num, :index => 1, :type => Integer, :
|
197
|
-
|
155
|
+
class_eval do
|
156
|
+
option :num, :index => 1, :type => Integer, :lookup => proc {|s| raise StandardError, "Hey, don't call me!"}
|
157
|
+
|
158
|
+
def execute
|
159
|
+
@num
|
160
|
+
end
|
198
161
|
end
|
199
162
|
lambda {
|
200
|
-
|
201
|
-
}.should_not raise_error
|
163
|
+
@function.call(5)
|
164
|
+
}.should_not raise_error
|
202
165
|
end
|
203
166
|
|
204
|
-
it "
|
205
|
-
|
206
|
-
option :
|
207
|
-
execute {@bool}
|
167
|
+
it "accepts nil as a valid instance of all types" do
|
168
|
+
class_eval do
|
169
|
+
option :num, :index => 1, :type => Numeric
|
208
170
|
end
|
209
171
|
|
210
|
-
|
211
|
-
|
212
|
-
MultipleValidOptionTypes(true).should be(true)
|
213
|
-
MultipleValidOptionTypes(false).should be(false)
|
214
|
-
end
|
215
|
-
|
216
|
-
it "raises an exception if it can't tell how to find a complex type" do
|
217
|
-
weird = Class.new
|
218
|
-
function :TakesWeirdObject do
|
219
|
-
option :weird, :type => weird
|
220
|
-
execute {@weird}
|
221
|
-
end
|
222
|
-
|
223
|
-
lambda {
|
224
|
-
TakesWeirdObject(:weird => "weird")
|
225
|
-
}.should raise_error
|
226
|
-
|
227
|
-
werd = weird.new
|
228
|
-
TakesWeirdObject(:weird => werd).should == werd
|
172
|
+
@function.call.should be_nil
|
173
|
+
@function.call(:num => nil).should be_nil
|
229
174
|
end
|
230
175
|
|
231
176
|
it "triggers an error if an option is required and after trying to find it, it is still nil." do
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
option :obj, :index => 1, :required => true, :
|
236
|
-
execute
|
177
|
+
objects = {:foo => 'bar', :baz => "bang" }
|
178
|
+
|
179
|
+
class_eval do
|
180
|
+
option :obj, :index => 1, :required => true, :lookup => proc {|key| objects[key]}
|
181
|
+
def execute
|
182
|
+
@obj
|
183
|
+
end
|
237
184
|
end
|
238
185
|
|
239
|
-
|
240
|
-
|
241
|
-
lambda {
|
242
|
-
|
243
|
-
}.should raise_error
|
186
|
+
@function.call(:foo).should == 'bar'
|
187
|
+
@function.call(:baz).should == 'bang'
|
188
|
+
lambda {
|
189
|
+
@function.call(:bif)
|
190
|
+
}.should raise_error
|
244
191
|
end
|
245
192
|
|
246
193
|
it "triggers an error if an option is unknown" do
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
Noop(:foo => 'bar')
|
251
|
-
}.should raise_error(AppKernel::FunctionCallError)
|
194
|
+
lambda {
|
195
|
+
@function.call(:foo => 'bar')
|
196
|
+
}.should raise_error(AppKernel::OptionsError)
|
252
197
|
end
|
253
198
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
199
|
+
|
200
|
+
describe "Default Values" do
|
201
|
+
it "allows for any option to have a default value" do
|
202
|
+
class_eval do
|
203
|
+
option :value, :default => 5
|
204
|
+
|
205
|
+
def execute
|
206
|
+
@value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
@function.call.should == 5
|
211
|
+
end
|
212
|
+
|
213
|
+
it "requires that the default value be the same as the option type if that is specified" do
|
214
|
+
lambda {
|
215
|
+
class_eval do
|
216
|
+
option :value, :type => Integer, :default => "NOT_INT"
|
217
|
+
end
|
218
|
+
}.should raise_error(AppKernel::IllegalOptionError)
|
258
219
|
end
|
259
220
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
end
|
221
|
+
it "sets a default option even if that option is explicitly passed in as nil" do
|
222
|
+
class_eval do
|
223
|
+
option :value, :default => 'fun'
|
264
224
|
|
265
|
-
|
266
|
-
|
267
|
-
|
225
|
+
def execute
|
226
|
+
@value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
@function.call(:value => nil).should == 'fun'
|
230
|
+
end
|
231
|
+
|
232
|
+
it "allows false as a default value" do
|
233
|
+
class_eval do
|
234
|
+
option :bool, :index => 1, :default => false
|
235
|
+
def execute
|
236
|
+
@bool
|
237
|
+
end
|
238
|
+
end
|
239
|
+
@function.call().should be(false)
|
240
|
+
end
|
268
241
|
end
|
242
|
+
|
243
|
+
describe "Complex Types" do
|
244
|
+
it "takes options that have multiple valid types" do
|
245
|
+
class_eval do
|
246
|
+
option :bool, :index => 1, :type => [TrueClass, FalseClass], :parse => proc {|s| s == "true"}
|
247
|
+
def execute
|
248
|
+
@bool
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
@function.call("true").should be(true)
|
253
|
+
@function.call("false").should be(false)
|
254
|
+
@function.call(true).should be(true)
|
255
|
+
@function.call(false).should be(false)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "raises an exception if it can't tell how to find a complex type" do
|
259
|
+
weird = Class.new
|
260
|
+
class_eval do
|
261
|
+
option :weird, :type => weird
|
262
|
+
def execute
|
263
|
+
@weird
|
264
|
+
end
|
265
|
+
end
|
266
|
+
lambda {
|
267
|
+
@function.call(:weird => "weird")
|
268
|
+
}.should raise_error
|
269
|
+
|
270
|
+
weird.new.tap do |w|
|
271
|
+
@function.call(:weird => w).should == w
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
269
276
|
end
|
270
|
-
|
271
|
-
end
|
277
|
+
|
278
|
+
end
|