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.
@@ -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