appkernel 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +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
|