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 CHANGED
@@ -1,6 +1,15 @@
1
- == 0.1.3 YYY-MM-DD
2
1
 
3
- * x major enhancements
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/validation.rb
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/validation_spec.rb
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.1.3"
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-11-17}
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/validation.rb", "script/console", "script/destroy", "script/generate", "spec/appkernel/function_spec.rb", "spec/appkernel/validation_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
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
@@ -1,6 +1,7 @@
1
1
 
2
2
  class AppKernel
3
- VERSION = "0.1.3"
3
+ VERSION = "0.2.0"
4
4
  require 'appkernel/function'
5
- require 'appkernel/validation'
5
+ require 'appkernel/curry'
6
+ require 'appkernel/types'
6
7
  end
@@ -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
@@ -1,237 +1,237 @@
1
- require 'set'
1
+ class AppKernel
2
2
 
3
- class AppKernel
4
- module Function
5
-
6
- def apply(fun, *args)
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 FunctionApplication
35
-
36
- attr_reader :return_value, :errors, :function, :args
37
-
38
- def initialize(fun, *args)
39
- @function = fun
40
- @errors = {}
41
- @args = Arguments.new(self, *args)
42
- @return_value = self.class.do_apply(self)
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
- def successful?
46
- @errors.empty?
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
- def options
50
- @args.canonical
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
- class Arguments
69
+
70
+ module ClassMethods
54
71
 
55
- attr_reader :canonical
56
-
57
- def initialize(app, *args)
58
- fun = app.function
59
- @app = app
60
- @canonical = {}
61
- @required = Set.new(fun.options.values.select {|o| o.required?})
62
- @optorder = fun.indexed_options
63
-
64
- for arg in args
65
- if (arg.is_a?(Hash))
66
- arg.each do |k,v|
67
- if opt = fun.options[k.to_sym]
68
- set opt, v
69
- else
70
- raise FunctionCallError, "#{fun.name}: unknown option :#{k}"
71
- end
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
- def set(opt, value)
92
- resolved = opt.resolve(@app, value)
93
- if !resolved.nil?
94
- @canonical[opt.name] = resolved
95
- @required.delete opt
96
- elsif !value.nil? && opt.required?
97
- @required.delete opt
98
- @required.delete opt
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
- class << self
105
-
106
- def apply_or_die(fun, *args)
107
- app = new(fun, *args)
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 do_apply(app)
116
- fun = app.function
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
- def to_s
161
- "#{@name}()"
162
- end
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
- def validate!
178
- if @default
179
- if @types
180
- raise OptionError, "#{@default} is not a kind of #{@types.join('|')}" unless @types.detect {|t| @default.kind_of?(t)}
181
- elsif @required
182
- Kernel.warn "option '#{@name}' unecessarily marked as required. It has a default value"
183
- end
184
- end
135
+ attr_reader :options
136
+
137
+ def initialize
138
+ @options = {}
139
+ @indexed = []
140
+ @required = []
141
+ @defaults = []
142
+ @presets = {}
185
143
  end
186
-
187
- def required?
188
- @required
144
+
145
+ def add(name, modifiers)
146
+ ingest Option.new(name, modifiers)
189
147
  end
190
148
 
191
- def optional?
192
- !@required
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 resolve(app, value)
196
- if value.nil?
197
- nil
198
- elsif @types
199
- if @types.detect {|t| value.is_a?(t)}
200
- value
201
- elsif @finder
202
- lookup(app, value)
203
- else
204
- raise OptionError, "Don't know how to convert #{value.class}:#{value} -> #{@type}"
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
- def lookup(app, value)
214
- result = @finder.call(value)
215
- app.errors[@name] = "couldn't find '#{@name}': #{value}" if result.nil?
216
- result
217
- end
218
- end
219
-
220
- class Validator
221
- end
222
- end
223
-
224
- class FunctionCallError < StandardError; end
225
- class OptionError < StandardError; end
226
-
227
- class ValidationError < StandardError
228
- def initialize(application)
229
- @app = application
230
- end
231
-
232
- def message
233
- "#{@app.function.name}: #{@app.errors.values.first}"
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