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