appkernel 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,13 @@
1
-
1
+ == 0.3.0 2010-04-12
2
+ * 3 major enhancement
3
+ * specify list parameters
4
+ * allow list parameters to be greedy and slurp lots of arguments.
5
+ * 3 minor enhancements
6
+ * automatically convert default values into canonical types.
7
+ * add a default conversion for URI::HTTP
8
+ * better error reporting
9
+
2
10
  == 0.2.0 2009-10-07
3
-
4
11
  * 4 major enhancements
5
12
  * functions now defined as classes, not via closures
6
13
  * validation method, and validation chaining
@@ -8,7 +15,6 @@
8
15
  * generic handlers for looking up appkernel values
9
16
 
10
17
  == 0.1.3 2009-08-31
11
-
12
18
  * 4 major enhancements
13
19
  * functions defined in the same scope can call themselves
14
20
  * validations can call function in the same module
@@ -16,15 +22,13 @@
16
22
  * allow multiple classes in the :type option attribute
17
23
 
18
24
  == 0.1.2 2009-07-01
19
-
20
- * 1 major enhancment:
25
+ * 1 major enhancement:
21
26
  * can't remember for the life of me what it was. Are you really reading this?
22
27
 
23
28
  * 1 major enhancement:
24
29
  * functioning prototype which allows you to create portable, self-validating functions
25
30
 
26
31
  == 0.1.1 2009-06-30
27
-
28
32
  * 1 major enhancement:
29
33
  * functioning prototype which allows you to create portable, self-validating functions
30
34
 
data/Rakefile CHANGED
@@ -1,28 +1,37 @@
1
1
  require 'rubygems'
2
- gem 'hoe', '>= 2.1.0'
3
- require 'hoe'
4
- require 'fileutils'
5
2
 
6
- $:.unshift File.dirname(__FILE__) + '/lib'
7
- require 'appkernel'
3
+ $gemspec = Gem::Specification.new do |gemspec|
4
+ manifest = Rake::FileList.new("**/*")
5
+ manifest.exclude "**/*.gem"
6
+ gemspec.name = "appkernel"
7
+ gemspec.version = "0.3.0"
8
+ gemspec.summary = "Functional Programming by Contract for Ruby"
9
+ gemspec.description = "validate, call, and curry your way to fun and profit!"
10
+ gemspec.email = "cowboyd@thefrontside.net"
11
+ gemspec.authors = ["Charles Lowell"]
12
+ gemspec.require_paths = ["lib"]
13
+ gemspec.files = manifest.to_a
14
+ end
15
+
16
+ desc "Build gem"
17
+ task :gem do
18
+ Gem::Builder.new($gemspec).build
19
+ end
8
20
 
9
- Hoe.plugin :newgem
10
- # Hoe.plugin :website
11
- # Hoe.plugin :cucumber_features
21
+ desc "Build gemspec"
22
+ task :gemspec do
23
+ File.open("#{$gemspec.name}.gemspec", "w") do |f|
24
+ f.write($gemspec.to_ruby)
25
+ end
26
+ end
27
+
28
+ task :clean do
29
+ sh "rm -rf *.gem"
30
+ end
12
31
 
13
- # Generate all the Rake tasks
14
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
15
- $hoe = Hoe.spec 'appkernel' do
16
- self.developer 'Charles Lowell', 'cowboyd@thefrontside.net'
17
- self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
18
- self.rubyforge_name = self.name # TODO this is default value
19
- # self.extra_deps = [['activesupport','>= 2.0.2']]
20
32
 
33
+ for file in Dir['tasks/*.rake']
34
+ load file
21
35
  end
22
36
 
23
- require 'newgem/tasks'
24
- Dir['tasks/**/*.rake'].each { |t| load t }
25
37
 
26
- # TODO - want other tests/tasks run by default? Add them to the list
27
- # remove_task :default
28
- # task :default => [:spec, :features]
@@ -2,34 +2,25 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{appkernel}
5
- s.version = "0.2.0"
5
+ s.version = "0.3.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Charles Lowell"]
9
- s.date = %q{2009-10-06}
10
- s.description = %q{AppKernel is a microframework for capturing your application in terms of minute, self-validating functions.
11
- Once defined, these functions can be used in rails, cocoa, an sms gateway, or wherever you want to take them.}
12
- s.email = ["cowboyd@thefrontside.net"]
13
- s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt"]
14
- s.files = ["History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "appkernel.gemspec", "lib/appkernel.rb", "lib/appkernel/curry.rb", "lib/appkernel/function.rb", "lib/appkernel/types.rb", "script/console", "script/destroy", "script/generate", "spec/appkernel/curry_spec.rb", "spec/appkernel/function_spec.rb", "spec/appkernel/types_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
15
- s.homepage = %q{http://github.com/cowboyd/appkernel}
16
- s.post_install_message = %q{PostInstall.txt}
17
- s.rdoc_options = ["--main", "README.rdoc"]
9
+ s.date = %q{2010-04-12}
10
+ s.description = %q{validate, call, and curry your way to fun and profit!}
11
+ s.email = %q{cowboyd@thefrontside.net}
12
+ s.files = ["appkernel.gemspec", "History.txt", "lib", "lib/appkernel", "lib/appkernel/curry.rb", "lib/appkernel/function.rb", "lib/appkernel/tap.rb", "lib/appkernel/types.rb", "lib/appkernel.rb", "pkg", "PostInstall.txt", "Rakefile", "README.rdoc", "script", "script/console", "script/destroy", "script/generate", "spec", "spec/appkernel", "spec/appkernel/curry_spec.rb", "spec/appkernel/function_spec.rb", "spec/appkernel/types_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks", "tasks/rspec.rake"]
18
13
  s.require_paths = ["lib"]
19
- s.rubyforge_project = %q{appkernel}
20
- s.rubygems_version = %q{1.3.5}
21
- s.summary = %q{AppKernel is a microframework for capturing your application in terms of minute, self-validating functions}
14
+ s.rubygems_version = %q{1.3.6}
15
+ s.summary = %q{Functional Programming by Contract for Ruby}
22
16
 
23
17
  if s.respond_to? :specification_version then
24
18
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
19
  s.specification_version = 3
26
20
 
27
21
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
- s.add_development_dependency(%q<hoe>, [">= 2.3.3"])
29
22
  else
30
- s.add_dependency(%q<hoe>, [">= 2.3.3"])
31
23
  end
32
24
  else
33
- s.add_dependency(%q<hoe>, [">= 2.3.3"])
34
25
  end
35
26
  end
@@ -4,4 +4,5 @@ class AppKernel
4
4
  require 'appkernel/function'
5
5
  require 'appkernel/curry'
6
6
  require 'appkernel/types'
7
+ require 'appkernel/tap'
7
8
  end
@@ -9,14 +9,17 @@ class AppKernel
9
9
 
10
10
  class Options
11
11
  def curry(parent, params)
12
- @presets.merge! parent.canonicalize([params], nil, false)
12
+ errors = Errors.new
13
+ presets = parent.canonicalize([params], errors, false)
14
+ raise ArgumentError, errors.all.join('; ') unless errors.empty?
15
+ @presets.merge! presets
13
16
  applied, unapplied = parent.options.values.partition {|o| @presets.has_key?(o.name)}
14
17
  unapplied.each do |option|
15
18
  ingest option
16
19
  end
17
20
  applied.each do |option|
18
21
  if option.required? && @presets[option.name].nil?
19
- raise AppKernel::OptionsError, "required option '#{option.name}' may not be nil"
22
+ raise ArgumentError, "required option '#{option.name}' may not be nil"
20
23
  end
21
24
  end
22
25
  end
@@ -1,12 +1,4 @@
1
1
  class AppKernel
2
-
3
- class OptionsError < ArgumentError
4
-
5
- def initialize(errors)
6
- super(errors.first)
7
- end
8
-
9
- end
10
2
 
11
3
  class IllegalOptionError < StandardError; end
12
4
 
@@ -33,11 +25,13 @@ class AppKernel
33
25
  end
34
26
 
35
27
  def verify!
36
- raise OptionsError, @errors unless successful?
28
+ raise ArgumentError, @errors.all.join('; ') unless successful?
37
29
  end
38
30
  end
39
31
 
40
32
  class Errors
33
+ attr_reader :all
34
+
41
35
  include Enumerable
42
36
 
43
37
  def initialize
@@ -79,6 +73,18 @@ class AppKernel
79
73
 
80
74
  def prepare!
81
75
  @options = Options.new
76
+ call = Module.new.tap do |mod|
77
+ unless self.name.nil? || self.name.empty?
78
+ fun = self
79
+ path = self.name.split(/::/)
80
+ simple_name = path[path.length - 1]
81
+ fun_name = simple_name.gsub(/(\w)([A-Z])([a-z])/) {"#{$1}_#{$2.downcase}#{$3}"}.downcase
82
+ mod.send(:define_method, fun_name) do |*args|
83
+ fun.call(*args)
84
+ end
85
+ end
86
+ end
87
+ self.const_set(:Call, call)
82
88
  end
83
89
 
84
90
  def call(*args)
@@ -108,8 +114,8 @@ class AppKernel
108
114
  @errors = errors
109
115
  end
110
116
 
111
- def check(condition, message = "valditation failed")
112
- @errors.add(nil, message) unless condition
117
+ def check(condition, message = "valditation failed", tag=nil)
118
+ @errors.add(tag, message) unless condition
113
119
  end
114
120
  end
115
121
 
@@ -140,6 +146,7 @@ class AppKernel
140
146
  @required = []
141
147
  @defaults = []
142
148
  @presets = {}
149
+ @greedy = nil
143
150
  end
144
151
 
145
152
  def add(name, modifiers)
@@ -148,63 +155,127 @@ class AppKernel
148
155
 
149
156
  def ingest(o)
150
157
  @options[o.name] = o
151
- if o.index
152
- @indexed[o.index] = o
153
- @indexed.compact!
154
- end
158
+ @indexed[o.index] = o if o.index
155
159
  @required << o.name if o.required?
156
- @defaults << o.name if o.default?
160
+ @defaults << o.name if o.default?
157
161
  if o.default? && o.type
158
- raise IllegalOptionError, "option '#{o.name}' is not a #{o.type}" unless o.default.kind_of?(o.type)
159
- end
162
+ if Enumerable === o.type
163
+ if not o.type.detect {|t| o.default.kind_of?(t)}
164
+ raise IllegalOptionError, "default value #{o.default.inspect} for option '#{o.name}' is not in #{o.type.inspect}"
165
+ end
166
+ elsif not o.default.kind_of?(o.type)
167
+ raise IllegalOptionError, "default value #{o.default.inspect} for option '#{o.name}' is not a #{o.type}"
168
+ end
169
+ end
170
+ if o.greedy?
171
+ raise IllegalOptionError, "a function may not have more than one greedy option. has (#{@greedy.name}, #{o.name})" if @greedy
172
+ @greedy = o
173
+ end
174
+ raise IllegalOptionError, "a greedy option may not have an index" if o.greedy? && o.index
160
175
  end
161
176
 
177
+
178
+
179
+ #first, exctract all hash options
180
+ #if we find one we love, use it
181
+ #otherwise, if there's a slurpy option, throw it on the pile
182
+ #otherwise, it's an error.
162
183
  def canonicalize(args, errors, augment = true)
184
+ indexed = @indexed.compact
185
+ positionals, parameters, rest = comb(args)
186
+ unless @greedy
187
+ errors.add(nil,"too many arguments (#{positionals.length} for #{indexed.length})") if positionals.length > indexed.length
188
+ for hash in rest
189
+ for k,v in hash
190
+ errors.add(k, "unknown option '#{k}'")
191
+ end
192
+ end
193
+ return unless errors.empty?
194
+ end
163
195
  @presets.dup.tap do |canonical|
164
- indexed = @indexed.dup
165
- for arg in args
166
- case arg
167
- when Hash
168
- resolved = {}
169
- for k,v in arg
170
- if opt = @options[k]
171
- resolved[k] = opt.resolve(v)
172
- else
173
- raise OptionsError, "unknown option '#{k}'"
174
- end
175
- end
176
- canonical.merge! resolved
177
- else
178
- if opt = indexed.shift
179
- canonical[opt.name] = opt.resolve arg
180
- else
181
- raise ArgumentError, "too many arguments"
182
- end
196
+ canonical[@greedy.name] = rest if @greedy
197
+ for name,value in parameters
198
+ canonical[name] = @options[name].resolve(value)
199
+ end
200
+ positionals.length.times do |i|
201
+ if opt = indexed[i]
202
+ canonical[opt.name] = opt.resolve(positionals[i])
183
203
  end
184
204
  end
205
+ if @greedy
206
+ canonical[@greedy.name] = @greedy.resolve(rest)
207
+ end
185
208
  if augment
186
209
  for k in @defaults
187
210
  canonical[k] = @options[k].default unless canonical[k]
188
211
  end
189
- canonical.reject! {|k,v| v.nil?}
212
+ canonical.reject! {|k,v| v.nil? || (@options[k] && @options[k].list? && v.empty?)}
190
213
  for k in @required - canonical.keys
191
214
  errors.add(k, "missing required option '#{k}'")
192
215
  end
216
+ end
217
+ end
218
+ end
219
+
220
+ def comb(args)
221
+ positionals = []
222
+ parameters = {}
223
+ rest_parameters = {}
224
+ rest = []
225
+ index = @indexed.compact.length
226
+ for arg in args
227
+ if Hash === arg
228
+ for name,value in arg
229
+ key = name.to_sym
230
+ if opt = @options[key]
231
+ parameters[opt.name] = value
232
+ else
233
+ rest_parameters[key] = value
234
+ end
235
+ end
236
+ elsif index > 0
237
+ index -= 1
238
+ positionals << arg
239
+ else
240
+ rest << arg
193
241
  end
194
242
  end
243
+ rest << rest_parameters unless rest_parameters.empty?
244
+ return positionals, parameters, rest
195
245
  end
196
246
 
197
247
  class Option
248
+
249
+ class ::Symbol
250
+ def [](*dontcare)
251
+ Option.new(self, :list => true)
252
+ end
198
253
 
199
- attr_reader :name, :index, :type, :default
254
+ def *(*dontcare)
255
+ Option.new(self, :list => true, :greedy => true)
256
+ end
257
+ end
258
+
259
+ attr_reader :name, :index, :type, :default, :modifiers
200
260
 
201
- def initialize(name, modifiers)
261
+ def initialize(name, modifiers = {})
262
+ if name.kind_of?(Option)
263
+ modifiers = modifiers.merge(name.modifiers)
264
+ end
202
265
  @name = name.to_sym
266
+ @modifiers = modifiers
203
267
  @index = modifiers[:index]
204
268
  @required = modifiers[:required] == true
205
269
  @lookup = modifiers[:lookup] || modifiers[:parse]
206
270
  @type = modifiers[:type]
207
- @default = modifiers[:default]
271
+ @list = modifiers[:list]
272
+ @greedy = modifiers[:greedy]
273
+ begin
274
+ @default = resolve(modifiers[:default])
275
+ rescue StandardError => e
276
+ raise IllegalOptionError, "invalid default value for option '#{name}': #{e.message}"
277
+ end
278
+
208
279
  end
209
280
 
210
281
  def required?
@@ -214,18 +285,36 @@ class AppKernel
214
285
  def default?
215
286
  !@default.nil?
216
287
  end
288
+
289
+ def list?
290
+ @list
291
+ end
292
+
293
+ def greedy?
294
+ @greedy
295
+ end
296
+
297
+ def to_sym
298
+ @name
299
+ end
217
300
 
218
- def resolve(o)
219
- if o.nil? then nil
301
+ def resolve(o, single = false)
302
+ if o.nil? then nil
303
+ elsif @list && !single
304
+ o.kind_of?(Array) ? o.map {|v| resolve(v,true)} : [resolve(o, true)]
220
305
  elsif @type
221
306
  if @type.kind_of?(Class) && o.kind_of?(@type) then o
222
307
  elsif @type.kind_of?(Enumerable) && @type.detect {|t| o.kind_of?(t)} then o
223
308
  elsif @lookup
224
309
  @lookup.call(o)
225
310
  elsif @type.respond_to?(:to_option)
226
- @type.to_option(o)
311
+ begin
312
+ @type.to_option(o)
313
+ rescue StandardError => e
314
+ raise ArgumentError, "don't know how to convert #{o} into #{@type}: #{e}"
315
+ end
227
316
  else
228
- raise OptionsError, "don't know how to convert #{o} into #{@type}"
317
+ raise ArgumentError, "don't know how to convert #{o} into #{@type}"
229
318
  end
230
319
  else
231
320
  @lookup ? @lookup.call(o) : o
@@ -0,0 +1,8 @@
1
+ unless Object.method_defined?(:tap)
2
+ class Object
3
+ def tap
4
+ yield self
5
+ self
6
+ end
7
+ end
8
+ end
@@ -22,4 +22,21 @@ class AppKernel
22
22
  false
23
23
  end
24
24
  end
25
+ end
26
+
27
+ require 'net/http'
28
+ class URI::HTTP
29
+ def self.to_option(spec)
30
+ uri = URI.parse(spec)
31
+ case uri.scheme
32
+ when "http" then uri
33
+ when nil then to_option("http://#{spec}")
34
+ else
35
+ if uri.host.nil?
36
+ to_option("http://#{spec}")
37
+ else
38
+ raise "#{spec.inspect} is not a valid http url"
39
+ end
40
+ end
41
+ end
25
42
  end
@@ -13,28 +13,26 @@ describe "Function Currying " do
13
13
  end
14
14
 
15
15
  end
16
- end
17
-
16
+ end
18
17
  end
19
18
 
20
19
  it "new functions to be created by fixing values of specified options" do
21
20
  @mult.curry(2).tap do |double|
22
21
  double.call(3).should == 6
23
22
  double.call(:rhs => 10).should == 20
24
- end
25
-
23
+ end
26
24
  end
27
25
 
28
26
  it "is an error to curry options that do not exists" do
29
27
  lambda {
30
28
  @mult.curry(:hello => "world")
31
- }.should raise_error(AppKernel::OptionsError)
29
+ }.should raise_error(ArgumentError)
32
30
  end
33
31
 
34
32
  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
33
  lambda {
36
34
  @mult.curry(:lhs => 2).call(:lhs => 4, :rhs => 5)
37
- }.should raise_error(AppKernel::OptionsError)
35
+ }.should raise_error(ArgumentError)
38
36
  end
39
37
 
40
38
  it "requires all curried options to go through resolution and type matching" do
@@ -53,7 +51,7 @@ describe "Function Currying " do
53
51
  it "is an error to have a nil value for a curried option if that option is required" do
54
52
  lambda {
55
53
  @mult.curry(:lhs => nil)
56
- }.should raise_error(AppKernel::OptionsError)
54
+ }.should raise_error(ArgumentError)
57
55
  end
58
56
 
59
57
  it "automatically nils out an option if it is curried with nil" do
@@ -2,7 +2,6 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe AppKernel::Function do
4
4
 
5
- # Called before each example.
6
5
  before(:each) do
7
6
  @function = Class.new(AppKernel::Function)
8
7
  end
@@ -10,8 +9,15 @@ describe AppKernel::Function do
10
9
  def class_eval(&block)
11
10
  @function.class_eval(&block)
12
11
  end
12
+
13
+ def funcall(*args)
14
+ @function.call(*args)
15
+ end
13
16
 
14
-
17
+ def funapply(*args)
18
+ @function.apply(*args)
19
+ end
20
+
15
21
  describe "Calling Conventions" do
16
22
  it "allows modules to define an function functions that takes options" do
17
23
  class_eval do
@@ -77,14 +83,31 @@ describe AppKernel::Function do
77
83
  end
78
84
  end
79
85
 
86
+ it "does not care what order the positional arguments were specified in" do
87
+ class_eval do
88
+ option :to, :index => 1
89
+ option :greeting, :index => 0
90
+
91
+ def execute
92
+ @greeting.should == "Hello"
93
+ @to.should == "World"
94
+ end
95
+ end
96
+
97
+ @function.call("Hello", "World")
98
+ end
99
+ end
100
+
101
+ describe "Error Handling" do
102
+
80
103
  it "raises an error immediately if you try to call a function that has invalid arguments" do
81
104
  class_eval do
82
105
  option :mandatory, :required => true
83
106
  end
84
107
 
85
108
  lambda {
86
- @function.call()
87
- }.should raise_error(AppKernel::OptionsError)
109
+ funcall()
110
+ }.should raise_error(ArgumentError)
88
111
  end
89
112
 
90
113
  it "allows validation of its arguments" do
@@ -104,6 +127,20 @@ describe AppKernel::Function do
104
127
  end
105
128
 
106
129
  end
130
+
131
+ it "allows associating arbitrary tags with errors" do
132
+ class_eval do
133
+ def validate(this)
134
+ this.check(false, "Not!", :tag)
135
+ end
136
+ end
137
+
138
+ @function.apply.errors[:tag].should_not be_nil
139
+ end
140
+
141
+ it "supports hash syntax for specifying error tags"
142
+
143
+ it "throws a tag instead of OptionsError validation error if tag is an instance of Error"
107
144
  end
108
145
 
109
146
  describe "Option Resolution" do
@@ -193,10 +230,9 @@ describe AppKernel::Function do
193
230
  it "triggers an error if an option is unknown" do
194
231
  lambda {
195
232
  @function.call(:foo => 'bar')
196
- }.should raise_error(AppKernel::OptionsError)
233
+ }.should raise_error(ArgumentError)
197
234
  end
198
-
199
-
235
+
200
236
  describe "Default Values" do
201
237
  it "allows for any option to have a default value" do
202
238
  class_eval do
@@ -213,11 +249,22 @@ describe AppKernel::Function do
213
249
  it "requires that the default value be the same as the option type if that is specified" do
214
250
  lambda {
215
251
  class_eval do
216
- option :value, :type => Integer, :default => "NOT_INT"
217
- end
252
+ option :value, :type => Integer, :default => Object.new
253
+ end
218
254
  }.should raise_error(AppKernel::IllegalOptionError)
219
255
  end
220
256
 
257
+ it "will try to convert default values into the option type before setting them" do
258
+ class_eval do
259
+ option :value, :type => Integer, :default => "1"
260
+ def execute
261
+ @value
262
+ end
263
+ end
264
+
265
+ @function.call.should == 1
266
+ end
267
+
221
268
  it "sets a default option even if that option is explicitly passed in as nil" do
222
269
  class_eval do
223
270
  option :value, :default => 'fun'
@@ -273,6 +320,134 @@ describe AppKernel::Function do
273
320
  end
274
321
  end
275
322
 
323
+ describe "Option with multiple values" do
324
+
325
+ it "can automatically convert themselves into an array" do
326
+ class_eval do
327
+ option :arguments[], :index => 0
328
+ def execute
329
+ @arguments
330
+ end
331
+ end
332
+ funcall(1).should == [1]
333
+ end
334
+
335
+ it "leaves argument alone if it is already an array" do
336
+ class_eval do
337
+ option :args[], :index => 0
338
+ def execute;@args;end
339
+ end
340
+ funcall([1]).should == [1]
341
+ end
342
+
343
+ it "converts all the arguments in an array to their expected types" do
344
+ class_eval do
345
+ option :integers*[], :type => Integer
346
+ def execute
347
+ @integers
348
+ end
349
+ end
350
+ funcall("1","2","3").should == [1,2,3]
351
+ end
352
+
353
+ it "must be non-empty if it is required" do
354
+ class_eval do
355
+ option :args[], :required => true, :index => 0
356
+ end
357
+ funapply(:args => []).should_not be_successful
358
+ end
359
+
360
+ end
361
+
362
+ describe "Greedy Options" do
363
+
364
+ it "can have a greedy option which slurps all remaining arguments" do
365
+ class_eval do
366
+ option :arguments*[]
367
+ def execute
368
+ @arguments
369
+ end
370
+ end
371
+ funcall(1,2,3,4).should == [1,2,3,4]
372
+ end
373
+
374
+ it "may not have more than one greedy option" do
375
+ expect {
376
+ class_eval do
377
+ option :greedy*[]
378
+ option :greedytoo*[]
379
+ end
380
+ }.to raise_error(AppKernel::IllegalOptionError)
381
+ end
382
+ it "may not have an index" do
383
+ expect {
384
+ class_eval do
385
+ option :greedy*[], :index => 0
386
+ end
387
+ }.to raise_error(AppKernel::IllegalOptionError)
388
+ end
389
+
390
+ it "defaults to the empty array" do
391
+ class_eval do
392
+ option :greedy*[]
393
+ end
394
+ def execute
395
+ @greedy.should == []
396
+ end
397
+ end
398
+
399
+ context "with hash options" do
400
+ it "will slurp all hashes from the end of the argument list" do
401
+ class_eval do
402
+ option :greedy*[]
403
+ end
404
+ def execute
405
+ @greedy.should == [1,2,3, {:foo => 'bar', :baz => 'bif'}]
406
+ end
407
+
408
+ @function.call(1,2,4, :foo => 'bar', :baz => 'bif')
409
+ end
410
+
411
+ it "will slurp hashes from the begining of the argument list" do
412
+ class_eval do
413
+ option :greedy*[]
414
+ def execute
415
+ @greedy.should == [1,2,3, {:foo => 'bar'}]
416
+ end
417
+ end
418
+ @function.call({:foo => 'bar'},1,2,3)
419
+ end
420
+
421
+ it "will extract those arguments which it knows, but leave those which it does not" do
422
+ class_eval do
423
+ option :known
424
+ option :greedy*[]
425
+
426
+ def execute
427
+ @known.should == 'factor'
428
+ @greedy.should == [1,2,{:three => 'four', :five => 'six'}]
429
+ end
430
+ end
431
+
432
+ @function.call(1,2,:known => 'factor', :three => 'four', :five => "six")
433
+ end
434
+
435
+ end
436
+ end
437
+ end
438
+
439
+ describe "Invocation" do
440
+ it "has a module that contains a method which will invoke that function with call" do
441
+ class AppKernel::TestFunction < AppKernel::Function
442
+ def execute
443
+ "hi there"
444
+ end
445
+ end
446
+ Object.new.tap do |obj|
447
+ obj.extend(AppKernel::TestFunction::Call)
448
+ obj.test_function().should == "hi there"
449
+ end
450
+ end
276
451
  end
277
452
 
278
453
  end
@@ -22,7 +22,7 @@ describe "Type Conversion" do
22
22
  it "can call with a boolean function" do
23
23
  function = Class.new(AppKernel::Function).class_eval do
24
24
  self.tap do
25
- option :bool, :type => AppKernel::Boolean, :index => 1
25
+ option :bool, :type => AppKernel::Boolean, :index => 1, :default => false
26
26
  def execute
27
27
  @bool
28
28
  end
@@ -44,6 +44,35 @@ describe "Type Conversion" do
44
44
  end
45
45
  end
46
46
 
47
+ require 'net/http'
48
+ describe URI::HTTP do
49
+ it "converts URI::HTTP" do
50
+ t(URI::HTTP, "http://google.com:2020/search").tap do |uri|
51
+ uri.should be_kind_of(URI::HTTP)
52
+ uri.scheme.should == "http"
53
+ uri.host.should == "google.com"
54
+ uri.port.should == 2020
55
+ uri.path.should == "/search"
56
+ end
57
+ end
58
+
59
+ it "can guess the protocol if you don't provide it'" do |uri|
60
+ t(URI::HTTP, "google.com:4098/search").tap do |uri|
61
+ uri.should be_kind_of(URI::HTTP)
62
+ uri.scheme.should == 'http'
63
+ uri.host.should == "google.com"
64
+ uri.port.should == 4098
65
+ uri.path.should == "/search"
66
+ end
67
+ end
68
+
69
+ it "does something, we know not yet what when you give some other protocol" do
70
+ lambda {
71
+ t(URI::HTTP, "ldap://funbones.com")
72
+ }.should raise_error(StandardError)
73
+ end
74
+ end
75
+
47
76
  def t(type, str)
48
77
  type.to_option(str)
49
78
  end
@@ -8,3 +8,8 @@ end
8
8
 
9
9
  $:.unshift(File.dirname(__FILE__) + '/../lib')
10
10
  require 'appkernel'
11
+
12
+ def rputs(msg)
13
+ puts(ERB::Util.h(msg) + "<br/>")
14
+ end
15
+
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new(:spec) do |spec|
4
+ spec.libs << 'lib' << 'spec'
5
+ spec.spec_files = FileList['spec/**/*_spec.rb']
6
+ end
7
+ rescue LoadError => e
8
+ desc "Run specs"
9
+ task :spec do
10
+ puts "rspec is required to run specs (gem install rspec)"
11
+ end
12
+ end
13
+
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appkernel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Charles Lowell
@@ -9,43 +14,29 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-10-06 00:00:00 -05:00
17
+ date: 2010-04-13 00:00:00 -05:00
13
18
  default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: hoe
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 2.3.3
24
- version:
25
- description: |-
26
- AppKernel is a microframework for capturing your application in terms of minute, self-validating functions.
27
- Once defined, these functions can be used in rails, cocoa, an sms gateway, or wherever you want to take them.
28
- email:
29
- - cowboyd@thefrontside.net
19
+ dependencies: []
20
+
21
+ description: validate, call, and curry your way to fun and profit!
22
+ email: cowboyd@thefrontside.net
30
23
  executables: []
31
24
 
32
25
  extensions: []
33
26
 
34
- extra_rdoc_files:
35
- - History.txt
36
- - Manifest.txt
37
- - PostInstall.txt
27
+ extra_rdoc_files: []
28
+
38
29
  files:
39
- - History.txt
40
- - Manifest.txt
41
- - PostInstall.txt
42
- - README.rdoc
43
- - Rakefile
44
30
  - appkernel.gemspec
45
- - lib/appkernel.rb
31
+ - History.txt
46
32
  - lib/appkernel/curry.rb
47
33
  - lib/appkernel/function.rb
34
+ - lib/appkernel/tap.rb
48
35
  - lib/appkernel/types.rb
36
+ - lib/appkernel.rb
37
+ - PostInstall.txt
38
+ - Rakefile
39
+ - README.rdoc
49
40
  - script/console
50
41
  - script/destroy
51
42
  - script/generate
@@ -54,34 +45,36 @@ files:
54
45
  - spec/appkernel/types_spec.rb
55
46
  - spec/spec.opts
56
47
  - spec/spec_helper.rb
48
+ - tasks/rspec.rake
57
49
  has_rdoc: true
58
- homepage: http://github.com/cowboyd/appkernel
50
+ homepage:
59
51
  licenses: []
60
52
 
61
- post_install_message: PostInstall.txt
62
- rdoc_options:
63
- - --main
64
- - README.rdoc
53
+ post_install_message:
54
+ rdoc_options: []
55
+
65
56
  require_paths:
66
57
  - lib
67
58
  required_ruby_version: !ruby/object:Gem::Requirement
68
59
  requirements:
69
60
  - - ">="
70
61
  - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
71
64
  version: "0"
72
- version:
73
65
  required_rubygems_version: !ruby/object:Gem::Requirement
74
66
  requirements:
75
67
  - - ">="
76
68
  - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
77
71
  version: "0"
78
- version:
79
72
  requirements: []
80
73
 
81
- rubyforge_project: appkernel
82
- rubygems_version: 1.3.5
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.6
83
76
  signing_key:
84
77
  specification_version: 3
85
- summary: AppKernel is a microframework for capturing your application in terms of minute, self-validating functions
78
+ summary: Functional Programming by Contract for Ruby
86
79
  test_files: []
87
80
 
@@ -1,18 +0,0 @@
1
- History.txt
2
- Manifest.txt
3
- PostInstall.txt
4
- README.rdoc
5
- Rakefile
6
- appkernel.gemspec
7
- lib/appkernel.rb
8
- lib/appkernel/curry.rb
9
- lib/appkernel/function.rb
10
- lib/appkernel/types.rb
11
- script/console
12
- script/destroy
13
- script/generate
14
- spec/appkernel/curry_spec.rb
15
- spec/appkernel/function_spec.rb
16
- spec/appkernel/types_spec.rb
17
- spec/spec.opts
18
- spec/spec_helper.rb