appkernel 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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