appkernel 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- @mod = Module.new do |mod|
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
- it "allows modules to define an function functions" do
18
- @mod.module_eval do
19
- function :Say do
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 do
19
+
20
+ def execute
23
21
  @word
24
22
  end
25
23
  end
26
- Say(:word => "hello").should == "hello"
27
- end
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
- Say("Hello", "Charles", :extra => "How are you?").should == "Hello Charles, How are you?"
41
- Say("Hello", "Charles").should == "Hello Charles"
42
- Say(:greeting => "Hello", :to => "Charles").should == "Hello Charles"
43
- end
44
-
45
- it "allows classes that include the module to also use the commands from that module" do
46
- @mod.module_eval do
47
- function :Included do
48
- execute do
49
- "It worked!"
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
- mod = @mod
54
- Class.new(Object).class_eval do
55
- include mod
56
- self
57
- end.new.instance_eval do
58
- Included().should == "It worked!"
59
- end
60
- end
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
- function :Second do
81
- execute do
82
- "Hello"
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
- First().should == "Hello"
87
- end
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
- end
96
- result = apply(@mod::FiveAlive, "candy bar")
97
- result.return_value.should == "FiveAlive is a candy bar"
98
- result.successful?.should be(true)
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
- lambda {
123
- Harpo()
124
- }.should raise_error(AppKernel::ValidationError)
125
- end
126
-
127
- it "allows validation of its arguments" do
128
- function :Picky do
129
- option :arg, :index => 1
130
- validate do
131
- @arg.check @arg == 5 && @arg != 6, "must be 5 and not be 6"
132
- end
133
- end
134
-
135
- apply(@mod::Picky, 5).successful?.should be(true)
136
- result = apply(@mod::Picky, 6)
137
- result.successful?.should be(false)
138
- result.errors[:arg].should == "must be 5 and not be 6"
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 find parameter in the function definition which tells it how to lookup arguments" do
147
- function :TakesInt do
148
- option :num, :index => 1, :find => proc {|s| s.to_i}
149
-
150
- execute do
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
- TakesInt("5").should == 5
118
+
119
+ @function.call("5").should == 5
156
120
  end
157
121
 
158
- describe "Default Values" do
159
- it "allows for any option to have a default value" do
160
- function :HasDefault do
161
- option :value, :default => 5
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
- HasDefault().should == 5
167
- end
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
- it "warns if an option has a default and is also required" do
178
- Kernel.should_receive(:warn)
179
- function :QuestionableDefault do
180
- option :value, :required => true, :default => 5
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
- it "sets a default option even if that option is explicitly passed in as nil" do
185
- function :ExplicitNil do
186
- option :value, :default => 'fun'
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
- function :TakesInt do
196
- option :num, :index => 1, :type => Integer, :find => proc {|s| raise StandardError, "Hey, don't call me!"}
197
- execute {@num}
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
- TakesInt(5).should == 5
201
- }.should_not raise_error
163
+ @function.call(5)
164
+ }.should_not raise_error
202
165
  end
203
166
 
204
- it "an option can have multiple valid types" do
205
- function :MultipleValidOptionTypes do
206
- option :bool, :index => 1, :type => [TrueClass, FalseClass], :find => proc {|s| s == "true"}
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
- MultipleValidOptionTypes("true").should be(true)
211
- MultipleValidOptionTypes("false").should be(false)
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
- objects = {:foo => 'bar', :baz => "bang", }
233
-
234
- function :Lookup do
235
- option :obj, :index => 1, :required => true, :find => proc {|key| objects[key]}
236
- execute {@obj}
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
- Lookup(:foo).should == 'bar'
240
- Lookup(:baz).should == 'bang'
241
- lambda {
242
- Lookup(:bif)
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
- function(:Noop) {}
248
- Noop()
249
- lambda {
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
- it "allows false as a default value" do
255
- function :FalseDefault do
256
- option :bool, :default => false
257
- execute {@bool}
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
- FalseDefault().should be(false)
261
- end
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
- def function(sym, &body)
266
- @mod.module_eval do
267
- function sym, &body
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