appkernel 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -6
- data/Rakefile +29 -20
- data/appkernel.gemspec +7 -16
- data/lib/appkernel.rb +1 -0
- data/lib/appkernel/curry.rb +5 -2
- data/lib/appkernel/function.rb +134 -45
- data/lib/appkernel/tap.rb +8 -0
- data/lib/appkernel/types.rb +17 -0
- data/spec/appkernel/curry_spec.rb +5 -7
- data/spec/appkernel/function_spec.rb +184 -9
- data/spec/appkernel/types_spec.rb +30 -1
- data/spec/spec_helper.rb +5 -0
- data/tasks/rspec.rake +13 -0
- metadata +31 -38
- data/Manifest.txt +0 -18
data/History.txt
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
#
|
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]
|
data/appkernel.gemspec
CHANGED
@@ -2,34 +2,25 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{appkernel}
|
5
|
-
s.version = "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{
|
10
|
-
s.description = %q{
|
11
|
-
|
12
|
-
s.
|
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.
|
20
|
-
s.
|
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
|
data/lib/appkernel.rb
CHANGED
data/lib/appkernel/curry.rb
CHANGED
@@ -9,14 +9,17 @@ class AppKernel
|
|
9
9
|
|
10
10
|
class Options
|
11
11
|
def curry(parent, params)
|
12
|
-
|
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
|
22
|
+
raise ArgumentError, "required option '#{option.name}' may not be nil"
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
data/lib/appkernel/function.rb
CHANGED
@@ -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
|
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(
|
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
|
-
|
159
|
-
|
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
|
-
|
165
|
-
for
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
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
|
data/lib/appkernel/types.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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
|
-
|
87
|
-
}.should raise_error(
|
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(
|
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 =>
|
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
|
data/spec/spec_helper.rb
CHANGED
data/tasks/rspec.rake
ADDED
@@ -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
|
-
|
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:
|
17
|
+
date: 2010-04-13 00:00:00 -05:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
-
|
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:
|
50
|
+
homepage:
|
59
51
|
licenses: []
|
60
52
|
|
61
|
-
post_install_message:
|
62
|
-
rdoc_options:
|
63
|
-
|
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:
|
82
|
-
rubygems_version: 1.3.
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.6
|
83
76
|
signing_key:
|
84
77
|
specification_version: 3
|
85
|
-
summary:
|
78
|
+
summary: Functional Programming by Contract for Ruby
|
86
79
|
test_files: []
|
87
80
|
|
data/Manifest.txt
DELETED
@@ -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
|