appkernel 0.1.0 → 0.1.1
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 +4 -0
- data/README.rdoc +1 -0
- data/lib/appkernel/function.rb +90 -34
- data/lib/appkernel/validation.rb +9 -5
- data/lib/appkernel.rb +1 -1
- data/spec/appkernel/function_spec.rb +97 -112
- metadata +1 -1
data/History.txt
CHANGED
data/README.rdoc
CHANGED
@@ -11,6 +11,7 @@ Once defined, these functions can be used in rails, cocoa, an sms gateway, or wh
|
|
11
11
|
|
12
12
|
* Package the bits of your application in terms of what it *does*, not the entities it contains.
|
13
13
|
* Get a command line interface for free (that's better than vanilla script/console)
|
14
|
+
* Transparent conversion between options and the strings that represent them.
|
14
15
|
|
15
16
|
== SYNOPSIS:
|
16
17
|
|
data/lib/appkernel/function.rb
CHANGED
@@ -10,7 +10,7 @@ class AppKernel
|
|
10
10
|
def self.included(mod)
|
11
11
|
class << mod
|
12
12
|
def function(symbol, &definition)
|
13
|
-
fun = ::AppKernel::FunctionDefinition.new(definition)
|
13
|
+
fun = ::AppKernel::FunctionDefinition.new(symbol, definition)
|
14
14
|
self.const_set(symbol, fun)
|
15
15
|
self.send(:define_method, symbol) do |*args|
|
16
16
|
FunctionApplication.apply_or_die(fun, *args)
|
@@ -33,13 +33,12 @@ class AppKernel
|
|
33
33
|
|
34
34
|
class FunctionApplication
|
35
35
|
|
36
|
-
attr_reader :return_value, :errors, :function, :
|
36
|
+
attr_reader :return_value, :errors, :function, :args
|
37
37
|
|
38
38
|
def initialize(fun, *args)
|
39
39
|
@function = fun
|
40
|
-
@args = args
|
41
|
-
@options = {}
|
42
40
|
@errors = {}
|
41
|
+
@args = Arguments.new(self, *args)
|
43
42
|
@return_value = self.class.do_apply(self, *args)
|
44
43
|
end
|
45
44
|
|
@@ -47,6 +46,47 @@ class AppKernel
|
|
47
46
|
@errors.empty?
|
48
47
|
end
|
49
48
|
|
49
|
+
def options
|
50
|
+
@args.canonical
|
51
|
+
end
|
52
|
+
|
53
|
+
class Arguments
|
54
|
+
|
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, "unknown option :#{@name}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
elsif opt = @optorder.shift
|
74
|
+
set opt, arg
|
75
|
+
end
|
76
|
+
end
|
77
|
+
for opt in @required
|
78
|
+
app.errors[opt.name] = "missing required option '#{@name}'"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def set(opt, value)
|
83
|
+
if resolved = opt.resolve(@app, value)
|
84
|
+
@canonical[opt.name] = resolved
|
85
|
+
@required.delete opt
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
50
90
|
class << self
|
51
91
|
|
52
92
|
def apply_or_die(fun, *args)
|
@@ -58,31 +98,16 @@ class AppKernel
|
|
58
98
|
end
|
59
99
|
end
|
60
100
|
|
61
|
-
def do_apply(app, *args)
|
62
|
-
fun = app.function
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
arg.each do |k,v|
|
69
|
-
if opt = fun.options[k.to_sym]
|
70
|
-
opt.set instance, v
|
71
|
-
required_options.delete opt
|
72
|
-
app.options[opt.name] = v
|
73
|
-
end
|
74
|
-
end
|
75
|
-
elsif opt = indexed_options.shift
|
76
|
-
opt.set instance, arg
|
77
|
-
required_options.delete opt
|
78
|
-
app.options[opt.name] = arg
|
101
|
+
def do_apply(app, *args)
|
102
|
+
fun = app.function
|
103
|
+
app.errors.merge! fun.validation.validate(app.options) if app.successful?
|
104
|
+
if app.successful?
|
105
|
+
scope = Object.new
|
106
|
+
for k,v in app.options do
|
107
|
+
scope.instance_variable_set("@#{k}", v)
|
79
108
|
end
|
109
|
+
scope.instance_eval &fun.impl
|
80
110
|
end
|
81
|
-
for opt in required_options do
|
82
|
-
app.errors[opt.name] = "Missing required option '#{opt.name}'"
|
83
|
-
end
|
84
|
-
app.errors.merge! fun.validation.validate(app.options)
|
85
|
-
instance.instance_eval &fun.impl if app.successful?
|
86
111
|
end
|
87
112
|
end
|
88
113
|
end
|
@@ -91,7 +116,8 @@ class AppKernel
|
|
91
116
|
|
92
117
|
attr_reader :impl, :options, :validation
|
93
118
|
|
94
|
-
def initialize(definition)
|
119
|
+
def initialize(name, definition)
|
120
|
+
@name = name
|
95
121
|
@options = {}
|
96
122
|
@impl = lambda {}
|
97
123
|
@validation = ::AppKernel::Validation::Validator.new
|
@@ -114,19 +140,22 @@ class AppKernel
|
|
114
140
|
def validate(&checks)
|
115
141
|
@validation = AppKernel::Validation::Validator.new(&checks)
|
116
142
|
end
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
"#{@name}()"
|
146
|
+
end
|
117
147
|
|
118
148
|
class Option
|
149
|
+
ID = lambda {|o| o}
|
119
150
|
attr_reader :name, :index
|
120
151
|
def initialize(name, params)
|
121
|
-
@name = name
|
152
|
+
@name = name.to_sym
|
122
153
|
@index = params[:index]
|
123
154
|
@required = params[:required] == true
|
155
|
+
@finder = params[:find]
|
156
|
+
@type = params[:type]
|
124
157
|
end
|
125
|
-
|
126
|
-
def set(o, value)
|
127
|
-
o.instance_variable_set("@#{@name}", value)
|
128
|
-
end
|
129
|
-
|
158
|
+
|
130
159
|
def required?
|
131
160
|
@required
|
132
161
|
end
|
@@ -134,12 +163,39 @@ class AppKernel
|
|
134
163
|
def optional?
|
135
164
|
!@required
|
136
165
|
end
|
166
|
+
|
167
|
+
def resolve(app, value)
|
168
|
+
if value.nil?
|
169
|
+
nil
|
170
|
+
elsif @type
|
171
|
+
if value.is_a?(@type)
|
172
|
+
value
|
173
|
+
elsif @finder
|
174
|
+
lookup(app, value)
|
175
|
+
else
|
176
|
+
raise FunctionDefinitionError, "Don't know how to convert #{value.class}:#{value} -> #{@type}"
|
177
|
+
end
|
178
|
+
elsif @finder
|
179
|
+
lookup(app, value)
|
180
|
+
else
|
181
|
+
value
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def lookup(app, value)
|
186
|
+
result = @finder.call(value)
|
187
|
+
app.errors[@name] = "couldn't find '#{@name}': #{value}" if result.nil?
|
188
|
+
result
|
189
|
+
end
|
190
|
+
|
137
191
|
end
|
138
192
|
|
139
193
|
class Validator
|
140
194
|
end
|
141
195
|
end
|
142
196
|
|
197
|
+
class FunctionCallError < StandardError; end
|
198
|
+
|
143
199
|
class ValidationError < StandardError
|
144
200
|
def initialize(application)
|
145
201
|
@app = application
|
data/lib/appkernel/validation.rb
CHANGED
@@ -17,14 +17,18 @@ module AppKernel::Validation
|
|
17
17
|
NilValue.new
|
18
18
|
when Fixnum
|
19
19
|
FixnumValue.new(v)
|
20
|
+
when Symbol
|
21
|
+
v
|
20
22
|
else
|
21
23
|
v.dup
|
22
24
|
end
|
23
|
-
val
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
unless Symbol === val
|
26
|
+
val.extend Check
|
27
|
+
val.instance_eval do
|
28
|
+
@_add_validation_error = lambda {|message|
|
29
|
+
errors[k] = message
|
30
|
+
}
|
31
|
+
end
|
28
32
|
end
|
29
33
|
scope.instance_variable_set("@#{k}", val)
|
30
34
|
end
|
data/lib/appkernel.rb
CHANGED
@@ -28,22 +28,18 @@ describe AppKernel::Function do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "allows certain options to be the default option" do
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
option :extra
|
36
|
-
|
37
|
-
execute do
|
38
|
-
"#{@greeting} #{@to}#{(', ' + @extra) if @extra}"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
31
|
+
function :Say do
|
32
|
+
option :greeting, :index => 0
|
33
|
+
option :to, :index => 1
|
34
|
+
option :extra
|
42
35
|
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
execute do
|
37
|
+
"#{@greeting} #{@to}#{(', ' + @extra) if @extra}"
|
38
|
+
end
|
46
39
|
end
|
40
|
+
Say("Hello", "Charles", :extra => "How are you?").should == "Hello Charles, How are you?"
|
41
|
+
Say("Hello", "Charles").should == "Hello Charles"
|
42
|
+
Say(:greeting => "Hello", :to => "Charles").should == "Hello Charles"
|
47
43
|
end
|
48
44
|
|
49
45
|
it "allows classes that include the module to also use the commands from that module" do
|
@@ -75,27 +71,23 @@ describe AppKernel::Function do
|
|
75
71
|
end
|
76
72
|
|
77
73
|
it "can be called by using the apply method instead of invoking it directly" do
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
end
|
74
|
+
function :FiveAlive do
|
75
|
+
option :desc, :index => 1
|
76
|
+
execute do
|
77
|
+
"FiveAlive is a #{@desc}"
|
78
|
+
end
|
79
|
+
end
|
86
80
|
result = apply(@mod::FiveAlive, "candy bar")
|
87
81
|
result.return_value.should == "FiveAlive is a candy bar"
|
88
82
|
result.successful?.should be(true)
|
89
83
|
end
|
90
84
|
|
91
85
|
it "can have required options, but they are never required by default" do
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
86
|
+
function :Say do
|
87
|
+
option :greeting, :index => 1, :required => true
|
88
|
+
option :to, :index => 2, :required => true
|
89
|
+
option :extra, :index => 3
|
90
|
+
end
|
99
91
|
result = apply(@mod::Say, "Hello", "World", "I'm doing fine.")
|
100
92
|
result.successful?.should be(true)
|
101
93
|
result = apply(@mod::Say)
|
@@ -107,10 +99,8 @@ describe AppKernel::Function do
|
|
107
99
|
end
|
108
100
|
|
109
101
|
it "raises an error immediately if you try to call a function that has invalid arguments" do
|
110
|
-
|
111
|
-
|
112
|
-
option :mandatory, :required => true
|
113
|
-
end
|
102
|
+
function :Harpo do
|
103
|
+
option :mandatory, :required => true
|
114
104
|
end
|
115
105
|
|
116
106
|
lambda {
|
@@ -119,14 +109,12 @@ describe AppKernel::Function do
|
|
119
109
|
end
|
120
110
|
|
121
111
|
it "allows validation of its arguments" do
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
end
|
112
|
+
function :Picky do
|
113
|
+
option :arg, :index => 1
|
114
|
+
validate do
|
115
|
+
@arg.check @arg == 5 && @arg != 6, "must be 5 and not be 6"
|
116
|
+
end
|
117
|
+
end
|
130
118
|
|
131
119
|
apply(@mod::Picky, 5).successful?.should be(true)
|
132
120
|
result = apply(@mod::Picky, 6)
|
@@ -137,75 +125,72 @@ describe AppKernel::Function do
|
|
137
125
|
result.successful?.should be(false)
|
138
126
|
result.errors[:arg].should == "must be 5 and not be 6"
|
139
127
|
end
|
128
|
+
|
129
|
+
describe "Option Resolution" do
|
130
|
+
it "can take a find parameter in the function definition which tells it how to lookup arguments" do
|
131
|
+
function :TakesInt do
|
132
|
+
option :num, :index => 1, :find => proc {|s| s.to_i}
|
133
|
+
|
134
|
+
execute do
|
135
|
+
@num
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
TakesInt("5").should == 5
|
140
|
+
end
|
141
|
+
|
142
|
+
it "doesn't do an argument conversion if the argument is already of the correct type" do
|
143
|
+
function :TakesInt do
|
144
|
+
option :num, :index => 1, :type => Integer, :find => proc {|s| raise StandardError, "Hey, don't call me!"}
|
145
|
+
execute {@num}
|
146
|
+
end
|
147
|
+
|
148
|
+
TakesInt(5).should == 5
|
149
|
+
end
|
150
|
+
|
151
|
+
it "raises an exception if it can't tell how to find a complex type" do
|
152
|
+
weird = Class.new
|
153
|
+
function :TakesWeirdObject do
|
154
|
+
option :weird, :type => weird
|
155
|
+
execute {@weird}
|
156
|
+
end
|
157
|
+
|
158
|
+
lambda {
|
159
|
+
TakesWeirdObject(:weird => "weird")
|
160
|
+
}.should raise_error
|
161
|
+
|
162
|
+
werd = weird.new
|
163
|
+
TakesWeirdObject(:weird => werd).should == werd
|
164
|
+
end
|
165
|
+
|
166
|
+
it "triggers an error if an option is required and after trying to find it, it is still nil." do
|
167
|
+
objects = {:foo => 'bar', :baz => "bang", }
|
168
|
+
|
169
|
+
function :Lookup do
|
170
|
+
option :obj, :index => 1, :required => true, :find => proc {|key| objects[key]}
|
171
|
+
execute {@obj}
|
172
|
+
end
|
173
|
+
|
174
|
+
Lookup(:foo).should == 'bar'
|
175
|
+
Lookup(:baz).should == 'bang'
|
176
|
+
lambda {
|
177
|
+
Lookup(:bif)
|
178
|
+
}.should raise_error
|
179
|
+
end
|
180
|
+
|
181
|
+
it "triggers an error if an option is unknown" do
|
182
|
+
function(:Noop) {}
|
183
|
+
Noop()
|
184
|
+
lambda {
|
185
|
+
Noop(:foo => 'bar')
|
186
|
+
}.should raise_error(AppKernel::FunctionCallError)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def function(sym, &body)
|
191
|
+
@mod.module_eval do
|
192
|
+
function sym, &body
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
140
196
|
end
|
141
|
-
|
142
|
-
|
143
|
-
# module Awacs::Ssid
|
144
|
-
# include AppKernel::Function
|
145
|
-
#
|
146
|
-
# function :GetErrorCountBySource do
|
147
|
-
# option :solr, :nil => false
|
148
|
-
# option :source, :nil => false, :empty => false
|
149
|
-
#
|
150
|
-
# item = Struct.new(:source, :error_count)
|
151
|
-
# execute do
|
152
|
-
# @solr.query("sys_error_marker:(\"SSID-AUGMENTATION-ERROR\")",
|
153
|
-
# :filter_queries => ["PublicationTitle_t:[* TO *]"],
|
154
|
-
# :rows => 0,
|
155
|
-
# :facets => {:fields => "sys_source_id", :mincount => 1, :limit => 1000, :sort => "count"}
|
156
|
-
# ).data['facet_counts']['facet_fields']['sys_source_id'].enum_for(:each_slice, 2).map do |slice|
|
157
|
-
# item.new(*slice)
|
158
|
-
# end
|
159
|
-
# end
|
160
|
-
# end
|
161
|
-
#
|
162
|
-
# function :GetErrorCountByPublicationTitle do
|
163
|
-
# option :solr, :nil => false
|
164
|
-
# option :source, :nil => false, :empty => false
|
165
|
-
# option :title, :nil => false, :empty => false
|
166
|
-
#
|
167
|
-
# item = Struct.new(:title, :error_count)
|
168
|
-
#
|
169
|
-
# execute do
|
170
|
-
# @solr.query(%Q{sys_error_marker:("SSID-AUGMENT-FAILED")},
|
171
|
-
# :filter_queries => %Q{sys_source_id:("#{@source}")},
|
172
|
-
# :facets => {:fields => ['PublicationTitle_sfacet'], :mincount => 1, :limit => 1000, :sort => :count}
|
173
|
-
# ).data['facet_counts']['facet_fields']['PublicationTitle_sfacet'].enum_for(:each_slice, 2).map do |slice|
|
174
|
-
# item.new(*slice)
|
175
|
-
# end
|
176
|
-
# end
|
177
|
-
# end
|
178
|
-
#
|
179
|
-
#
|
180
|
-
# executeable :AddMapping do
|
181
|
-
# option :type, :nil => false, :type => SsidMatchType, :lookup => proc {|s| SsidMatchType.find_by_name s}
|
182
|
-
# option :value, :nil => false
|
183
|
-
# option :ssid, :nil => false
|
184
|
-
#
|
185
|
-
# execute do
|
186
|
-
# SsidMapping.create :match_rule => FindOrCreateRule(:type => @type, :value => @value), :ssid => @ssid
|
187
|
-
# end
|
188
|
-
# end
|
189
|
-
#
|
190
|
-
# function :DeleteMapping do
|
191
|
-
# option :mapping, :index => 1, :type => SsidMapping, :lookup => proc {|s| SsidMapping.find(s)}
|
192
|
-
#
|
193
|
-
# execute do
|
194
|
-
# @mapping.delete!
|
195
|
-
# end
|
196
|
-
# end
|
197
|
-
|
198
|
-
# function :SaveMappings do
|
199
|
-
# option :adds, :default => [], :massage => proc {|o| [o].flatten}
|
200
|
-
# option :deletes, :default => [], :massage => proc {|o| [o].flatten}
|
201
|
-
# end
|
202
|
-
#
|
203
|
-
# end
|
204
|
-
#
|
205
|
-
# #later on....
|
206
|
-
# include Awacs::Ssid
|
207
|
-
# GetErrorCountBySource :source => "proquest"
|
208
|
-
# GetErrorCountByPublicationTitle, :source => "proquest", :title => "Journal of Water Law"
|
209
|
-
# AddMapping :rule => {:match_type => 'ISSN', :match_value => '001287'}, :ssid => '123459'
|
210
|
-
# AddMapping :rule => 89, :ssid => 1234569
|
211
|
-
#
|