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 CHANGED
@@ -2,3 +2,7 @@
2
2
 
3
3
  * 1 major enhancement:
4
4
  * functioning prototype which allows you to create portable, self-validating functions
5
+
6
+ == 0.1.0 2009-06-29
7
+ * 1 major enhancement:
8
+ * support for object resolution and required arguments
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
 
@@ -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, :options
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
- instance = Object.new
64
- indexed_options = fun.indexed_options
65
- required_options = Set.new(fun.options.values.select {|o| o.required?})
66
- for arg in args do
67
- if arg.kind_of?(Hash)
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
@@ -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.extend Check
24
- val.instance_eval do
25
- @_add_validation_error = lambda {|message|
26
- errors[k] = message
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
@@ -1,6 +1,6 @@
1
1
 
2
2
  class AppKernel
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  require 'appkernel/function'
5
5
  require 'appkernel/validation'
6
6
  end
@@ -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
- @mod.module_eval do
32
- function :Say do
33
- option :greeting, :index => 0
34
- option :to, :index => 1
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
- Say("Hello", "Charles", :extra => "How are you?").should == "Hello Charles, How are you?"
44
- Say("Hello", "Charles").should == "Hello Charles"
45
- Say(:greeting => "Hello", :to => "Charles").should == "Hello Charles"
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
- @mod.module_eval do
79
- function :FiveAlive do
80
- option :desc, :index => 1
81
- execute do
82
- "FiveAlive is a #{@desc}"
83
- end
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
- @mod.module_eval do
93
- function :Say do
94
- option :greeting, :index => 1, :required => true
95
- option :to, :index => 2, :required => true
96
- option :extra, :index => 3
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
- @mod.module_eval do
111
- function :Harpo do
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
- @mod.module_eval do
123
- function :Picky do
124
- option :arg, :index => 1
125
- validate do
126
- @arg.check @arg == 5 && @arg != 6, "must be 5 and not be 6"
127
- end
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
- #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appkernel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Lowell