appkernel 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -2
- data/Manifest.txt +4 -2
- data/appkernel.gemspec +3 -3
- data/lib/appkernel.rb +3 -2
- data/lib/appkernel/curry.rb +27 -0
- data/lib/appkernel/function.rb +206 -206
- data/lib/appkernel/types.rb +25 -0
- data/spec/appkernel/curry_spec.rb +75 -0
- data/spec/appkernel/function_spec.rb +220 -213
- data/spec/appkernel/types_spec.rb +55 -0
- metadata +6 -4
- data/lib/appkernel/validation.rb +0 -82
- data/spec/appkernel/validation_spec.rb +0 -32
data/History.txt
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
-
== 0.1.3 YYY-MM-DD
|
2
1
|
|
3
|
-
|
2
|
+
== 0.2.0 2009-10-07
|
3
|
+
|
4
|
+
* 4 major enhancements
|
5
|
+
* functions now defined as classes, not via closures
|
6
|
+
* validation method, and validation chaining
|
7
|
+
* function currying
|
8
|
+
* generic handlers for looking up appkernel values
|
9
|
+
|
10
|
+
== 0.1.3 2009-08-31
|
11
|
+
|
12
|
+
* 4 major enhancements
|
4
13
|
* functions defined in the same scope can call themselves
|
5
14
|
* validations can call function in the same module
|
6
15
|
* ability to provide defaults for function arguments.
|
data/Manifest.txt
CHANGED
@@ -5,12 +5,14 @@ README.rdoc
|
|
5
5
|
Rakefile
|
6
6
|
appkernel.gemspec
|
7
7
|
lib/appkernel.rb
|
8
|
+
lib/appkernel/curry.rb
|
8
9
|
lib/appkernel/function.rb
|
9
|
-
lib/appkernel/
|
10
|
+
lib/appkernel/types.rb
|
10
11
|
script/console
|
11
12
|
script/destroy
|
12
13
|
script/generate
|
14
|
+
spec/appkernel/curry_spec.rb
|
13
15
|
spec/appkernel/function_spec.rb
|
14
|
-
spec/appkernel/
|
16
|
+
spec/appkernel/types_spec.rb
|
15
17
|
spec/spec.opts
|
16
18
|
spec/spec_helper.rb
|
data/appkernel.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{appkernel}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.2.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-
|
9
|
+
s.date = %q{2009-10-06}
|
10
10
|
s.description = %q{AppKernel is a microframework for capturing your application in terms of minute, self-validating functions.
|
11
11
|
Once defined, these functions can be used in rails, cocoa, an sms gateway, or wherever you want to take them.}
|
12
12
|
s.email = ["cowboyd@thefrontside.net"]
|
13
13
|
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt"]
|
14
|
-
s.files = ["History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "lib/appkernel.rb", "lib/appkernel/function.rb", "lib/appkernel/
|
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
15
|
s.homepage = %q{http://github.com/cowboyd/appkernel}
|
16
16
|
s.post_install_message = %q{PostInstall.txt}
|
17
17
|
s.rdoc_options = ["--main", "README.rdoc"]
|
data/lib/appkernel.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
class AppKernel
|
3
|
+
class Function
|
4
|
+
def self.curry(options)
|
5
|
+
Class.new(self).tap do |c|
|
6
|
+
c.options.curry(@options, options)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Options
|
11
|
+
def curry(parent, params)
|
12
|
+
@presets.merge! parent.canonicalize([params], nil, false)
|
13
|
+
applied, unapplied = parent.options.values.partition {|o| @presets.has_key?(o.name)}
|
14
|
+
unapplied.each do |option|
|
15
|
+
ingest option
|
16
|
+
end
|
17
|
+
applied.each do |option|
|
18
|
+
if option.required? && @presets[option.name].nil?
|
19
|
+
raise AppKernel::OptionsError, "required option '#{option.name}' may not be nil"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
data/lib/appkernel/function.rb
CHANGED
@@ -1,237 +1,237 @@
|
|
1
|
-
|
1
|
+
class AppKernel
|
2
2
|
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
FunctionApplication.new(fun, *args)
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.included(mod)
|
11
|
-
class << mod
|
12
|
-
def function(symbol, &definition)
|
13
|
-
fun = ::AppKernel::FunctionDefinition.new(symbol, self, definition)
|
14
|
-
self.const_set(symbol, fun)
|
15
|
-
self.send(:define_method, symbol) do |*args|
|
16
|
-
FunctionApplication.apply_or_die(fun, *args)
|
17
|
-
end
|
18
|
-
if self.class == Module
|
19
|
-
self.send(:module_function, symbol)
|
20
|
-
else
|
21
|
-
class << self;self;end.send(:define_method, symbol) do |*args|
|
22
|
-
FunctionApplication.apply_or_die(fun, *args)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def apply(fun, *args)
|
28
|
-
FunctionApplication.new(fun, *args)
|
29
|
-
end
|
30
|
-
end
|
3
|
+
class OptionsError < ArgumentError
|
4
|
+
|
5
|
+
def initialize(errors)
|
6
|
+
super(errors.first)
|
31
7
|
end
|
8
|
+
|
32
9
|
end
|
33
10
|
|
34
|
-
class
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
11
|
+
class IllegalOptionError < StandardError; end
|
12
|
+
|
13
|
+
class Function
|
14
|
+
class << self
|
15
|
+
def inherited(subclass)
|
16
|
+
super(subclass)
|
17
|
+
subclass.send(:include, InstanceMethods)
|
18
|
+
subclass.extend(ClassMethods)
|
19
|
+
subclass.prepare!
|
20
|
+
end
|
43
21
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
22
|
+
|
23
|
+
class Result
|
24
|
+
attr_reader :errors
|
25
|
+
attr_accessor :return_value
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@errors = Errors.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def successful?
|
32
|
+
@errors.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify!
|
36
|
+
raise OptionsError, @errors unless successful?
|
37
|
+
end
|
47
38
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
39
|
+
|
40
|
+
class Errors
|
41
|
+
include Enumerable
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@errors = Hash.new {|h, k| h[k] = []}
|
45
|
+
@all = []
|
46
|
+
end
|
47
|
+
|
48
|
+
def add(tag, message)
|
49
|
+
@errors[tag] << message if tag
|
50
|
+
@all << message
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(&block)
|
54
|
+
@all.each(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](tag)
|
58
|
+
@errors[tag]
|
59
|
+
end
|
60
|
+
|
61
|
+
def length
|
62
|
+
@all.length
|
63
|
+
end
|
64
|
+
|
65
|
+
def empty?
|
66
|
+
@all.empty?
|
67
|
+
end
|
51
68
|
end
|
52
|
-
|
53
|
-
|
69
|
+
|
70
|
+
module ClassMethods
|
54
71
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
def options
|
73
|
+
@options
|
74
|
+
end
|
75
|
+
|
76
|
+
def option(name, modifiers = {})
|
77
|
+
@options.add(name, modifiers)
|
78
|
+
end
|
79
|
+
|
80
|
+
def prepare!
|
81
|
+
@options = Options.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def call(*args)
|
85
|
+
apply(*args).tap {|result|
|
86
|
+
result.verify!
|
87
|
+
}.return_value
|
88
|
+
end
|
89
|
+
|
90
|
+
def apply(*args)
|
91
|
+
Result.new.tap do |result|
|
92
|
+
@options.canonicalize(args, result.errors).tap do |params|
|
93
|
+
if result.successful?
|
94
|
+
new(params).tap do |function|
|
95
|
+
function.validate(Validator.new(result.errors))
|
96
|
+
if result.successful?
|
97
|
+
result.return_value = function.execute
|
98
|
+
end
|
99
|
+
end
|
72
100
|
end
|
73
|
-
elsif opt = @optorder.shift
|
74
|
-
set opt, arg
|
75
|
-
end
|
76
|
-
end
|
77
|
-
for opt in fun.options.values
|
78
|
-
if @canonical[opt.name].nil? && !opt.default.nil?
|
79
|
-
@canonical[opt.name] = opt.default
|
80
|
-
@required.delete opt
|
81
101
|
end
|
82
102
|
end
|
83
|
-
for opt in @required
|
84
|
-
app.errors[opt.name] = "missing required option '#{opt.name}'"
|
85
|
-
end
|
86
|
-
for name in fun.options.keys
|
87
|
-
@canonical[name] = nil if @canonical[name].nil?
|
88
|
-
end
|
89
103
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
@app.errors[opt.name] = "no such value '#{value}' for required option '#{opt.name}'"
|
100
|
-
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Validator
|
107
|
+
def initialize(errors)
|
108
|
+
@errors = errors
|
109
|
+
end
|
110
|
+
|
111
|
+
def check(condition, message = "valditation failed")
|
112
|
+
@errors.add(nil, message) unless condition
|
101
113
|
end
|
102
114
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
if app.successful?
|
109
|
-
app.return_value
|
110
|
-
else
|
111
|
-
raise ValidationError, app
|
115
|
+
|
116
|
+
module InstanceMethods
|
117
|
+
def initialize(params)
|
118
|
+
for k, v in params
|
119
|
+
self.instance_variable_set("@#{k}", v)
|
112
120
|
end
|
113
121
|
end
|
114
|
-
|
115
|
-
def
|
116
|
-
|
117
|
-
app.errors.merge! fun.validation.validate(app.options) if app.successful?
|
118
|
-
if app.successful?
|
119
|
-
scope = Object.new
|
120
|
-
scope.extend fun.mod
|
121
|
-
for k,v in app.options do
|
122
|
-
scope.instance_variable_set("@#{k}", v)
|
123
|
-
end
|
124
|
-
scope.instance_eval &fun.impl
|
125
|
-
end
|
122
|
+
|
123
|
+
def execute
|
124
|
+
#do something
|
126
125
|
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
class FunctionDefinition
|
131
|
-
|
132
|
-
attr_reader :name, :mod, :impl, :options, :validation
|
133
|
-
|
134
|
-
def initialize(name, mod, definition)
|
135
|
-
@name = name
|
136
|
-
@mod = mod
|
137
|
-
@options = {}
|
138
|
-
@impl = lambda {}
|
139
|
-
@validation = ::AppKernel::Validation::Validator.new(self)
|
140
|
-
self.instance_eval &definition
|
141
|
-
end
|
142
|
-
|
143
|
-
def option(name, params = {})
|
144
|
-
name = name.to_sym
|
145
|
-
@options[name] = Option.new(name, params)
|
146
|
-
end
|
147
|
-
|
148
|
-
def indexed_options
|
149
|
-
@options.values.select {|o| o.index}.sort_by {|a| a.index}
|
150
|
-
end
|
151
|
-
|
152
|
-
def execute(&impl)
|
153
|
-
@impl = impl
|
154
|
-
end
|
155
|
-
|
156
|
-
def validate(&checks)
|
157
|
-
@validation = AppKernel::Validation::Validator.new(self, &checks)
|
158
|
-
end
|
159
126
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
class Option
|
165
|
-
ID = lambda {|o| o}
|
166
|
-
attr_reader :name, :index, :default
|
167
|
-
def initialize(name, params)
|
168
|
-
@name = name.to_sym
|
169
|
-
@index = params[:index]
|
170
|
-
@required = params[:required] == true
|
171
|
-
@finder = params[:find]
|
172
|
-
@types = params[:type] ? [params[:type]].flatten : nil
|
173
|
-
@default = params[:default]
|
174
|
-
validate!
|
127
|
+
def validate(this)
|
128
|
+
|
129
|
+
#do something
|
175
130
|
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Options
|
176
134
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
135
|
+
attr_reader :options
|
136
|
+
|
137
|
+
def initialize
|
138
|
+
@options = {}
|
139
|
+
@indexed = []
|
140
|
+
@required = []
|
141
|
+
@defaults = []
|
142
|
+
@presets = {}
|
185
143
|
end
|
186
|
-
|
187
|
-
def
|
188
|
-
|
144
|
+
|
145
|
+
def add(name, modifiers)
|
146
|
+
ingest Option.new(name, modifiers)
|
189
147
|
end
|
190
148
|
|
191
|
-
def
|
192
|
-
|
149
|
+
def ingest(o)
|
150
|
+
@options[o.name] = o
|
151
|
+
if o.index
|
152
|
+
@indexed[o.index] = o
|
153
|
+
@indexed.compact!
|
154
|
+
end
|
155
|
+
@required << o.name if o.required?
|
156
|
+
@defaults << o.name if o.default?
|
157
|
+
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
|
193
160
|
end
|
194
|
-
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
161
|
+
|
162
|
+
def canonicalize(args, errors, augment = true)
|
163
|
+
@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
|
183
|
+
end
|
184
|
+
end
|
185
|
+
if augment
|
186
|
+
for k in @defaults
|
187
|
+
canonical[k] = @options[k].default unless canonical[k]
|
188
|
+
end
|
189
|
+
canonical.reject! {|k,v| v.nil?}
|
190
|
+
for k in @required - canonical.keys
|
191
|
+
errors.add(k, "missing required option '#{k}'")
|
192
|
+
end
|
205
193
|
end
|
206
|
-
elsif @finder
|
207
|
-
lookup(app, value)
|
208
|
-
else
|
209
|
-
value
|
210
194
|
end
|
211
195
|
end
|
212
196
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
197
|
+
class Option
|
198
|
+
|
199
|
+
attr_reader :name, :index, :type, :default
|
200
|
+
|
201
|
+
def initialize(name, modifiers)
|
202
|
+
@name = name.to_sym
|
203
|
+
@index = modifiers[:index]
|
204
|
+
@required = modifiers[:required] == true
|
205
|
+
@lookup = modifiers[:lookup] || modifiers[:parse]
|
206
|
+
@type = modifiers[:type]
|
207
|
+
@default = modifiers[:default]
|
208
|
+
end
|
209
|
+
|
210
|
+
def required?
|
211
|
+
@required
|
212
|
+
end
|
213
|
+
|
214
|
+
def default?
|
215
|
+
!@default.nil?
|
216
|
+
end
|
217
|
+
|
218
|
+
def resolve(o)
|
219
|
+
if o.nil? then nil
|
220
|
+
elsif @type
|
221
|
+
if @type.kind_of?(Class) && o.kind_of?(@type) then o
|
222
|
+
elsif @type.kind_of?(Enumerable) && @type.detect {|t| o.kind_of?(t)} then o
|
223
|
+
elsif @lookup
|
224
|
+
@lookup.call(o)
|
225
|
+
elsif @type.respond_to?(:to_option)
|
226
|
+
@type.to_option(o)
|
227
|
+
else
|
228
|
+
raise OptionsError, "don't know how to convert #{o} into #{@type}"
|
229
|
+
end
|
230
|
+
else
|
231
|
+
@lookup ? @lookup.call(o) : o
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
234
235
|
end
|
235
236
|
end
|
236
|
-
|
237
237
|
end
|