renee-core 0.3.11 → 0.4.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/lib/{renee_core.rb → renee/core.rb} +20 -13
  2. data/lib/{renee_core → renee/core}/chaining.rb +17 -15
  3. data/lib/{renee_core → renee/core}/env_accessors.rb +3 -3
  4. data/lib/{renee_core → renee/core}/exceptions.rb +0 -0
  5. data/lib/{renee_core → renee/core}/matcher.rb +0 -0
  6. data/lib/renee/core/plugins.rb +31 -0
  7. data/lib/{renee_core → renee/core}/rack_interaction.rb +0 -0
  8. data/lib/renee/core/request_context.rb +56 -0
  9. data/lib/{renee_core → renee/core}/responding.rb +3 -1
  10. data/lib/{renee_core → renee/core}/response.rb +0 -0
  11. data/lib/{renee_core → renee/core}/routing.rb +98 -101
  12. data/lib/{renee_core → renee/core}/transform.rb +0 -0
  13. data/test/{chaining_test.rb → renee-core/chaining_test.rb} +3 -3
  14. data/test/{env_accessors_test.rb → renee-core/env_accessors_test.rb} +1 -1
  15. data/test/{include_test.rb → renee-core/include_test.rb} +0 -0
  16. data/test/renee-core/request_context_test.rb +70 -0
  17. data/test/{responding_test.rb → renee-core/responding_test.rb} +0 -0
  18. data/test/{routing_test.rb → renee-core/routing_test.rb} +39 -82
  19. data/test/renee-core/test_helper.rb +4 -0
  20. data/test/{variable_type_test.rb → renee-core/variable_type_test.rb} +0 -0
  21. data/test/test_helper.rb +70 -4
  22. metadata +88 -129
  23. data/.yardopts +0 -6
  24. data/README.md +0 -242
  25. data/Rakefile +0 -13
  26. data/lib/renee_core/request_context.rb +0 -25
  27. data/lib/renee_core/url_generation.rb +0 -108
  28. data/lib/renee_core/version.rb +0 -6
  29. data/renee-core.gemspec +0 -26
  30. data/test/url_generation_test.rb +0 -66
@@ -1,15 +1,17 @@
1
1
  require 'rack'
2
- require 'renee_core/matcher'
3
- require 'renee_core/chaining'
4
- require 'renee_core/response'
5
- require 'renee_core/url_generation'
6
- require 'renee_core/exceptions'
7
- require 'renee_core/rack_interaction'
8
- require 'renee_core/request_context'
9
- require 'renee_core/transform'
10
- require 'renee_core/routing'
11
- require 'renee_core/responding'
12
- require 'renee_core/env_accessors'
2
+
3
+ require 'renee/version'
4
+ require 'renee/core/matcher'
5
+ require 'renee/core/chaining'
6
+ require 'renee/core/response'
7
+ require 'renee/core/exceptions'
8
+ require 'renee/core/rack_interaction'
9
+ require 'renee/core/request_context'
10
+ require 'renee/core/transform'
11
+ require 'renee/core/routing'
12
+ require 'renee/core/responding'
13
+ require 'renee/core/env_accessors'
14
+ require 'renee/core/plugins'
13
15
 
14
16
  # Top-level Renee constant
15
17
  module Renee
@@ -25,9 +27,15 @@ module Renee
25
27
  # For convience you can also used a method named #Renee
26
28
  # for decalaring new instances.
27
29
  class Core
30
+ # Current version of Renee::Core
31
+ VERSION = Renee::VERSION
32
+
33
+ # Error raised if routing fails. Use #continue_routing to continue routing.
34
+ NotMatchedError = Class.new(RuntimeError)
35
+
28
36
  # Class methods that are included in new instances of {Core}
29
37
  module ClassMethods
30
- include URLGeneration
38
+ include Plugins
31
39
 
32
40
  # The application block used to create your application.
33
41
  attr_reader :application_block
@@ -46,7 +54,6 @@ module Renee
46
54
  register_variable_type :integer, IntegerMatcher
47
55
  register_variable_type :int, :integer
48
56
  end
49
- self
50
57
  end
51
58
 
52
59
  # Runs class methods on your application.
@@ -13,29 +13,28 @@ module Renee
13
13
  module Chaining
14
14
  # @private
15
15
  class ChainingProxy
16
- def initialize(target, m, args)
16
+ def initialize(target, m, args = nil)
17
17
  @target, @calls = target, []
18
18
  @calls << [m, args]
19
19
  end
20
20
 
21
21
  def method_missing(m, *args, &blk)
22
22
  @calls << [m, args]
23
- if blk.nil? && @target.class.private_method_defined?(:"#{m}_without_chain")
23
+ if blk.nil? && @target.class.respond_to?(:chainable?) && @target.class.chainable?(m)
24
24
  self
25
25
  else
26
26
  inner_args = []
27
27
  ret = nil
28
28
  callback = proc do |*callback_args|
29
29
  inner_args.concat(callback_args)
30
- if @calls.size == 0 and blk
31
- blk.call(*inner_args)
30
+ if @calls.size == 0
31
+ ret = blk.call(*inner_args) if blk
32
32
  else
33
33
  call = @calls.shift
34
- ret = @target.send(call.at(0), *call.at(1), &callback)
34
+ ret = call.at(1) ? @target.send(call.at(0), *call.at(1), &callback) : @target.send(call.at(0), &callback)
35
35
  end
36
36
  end
37
- call = @calls.shift
38
- ret = @target.send(call.at(0), *call.at(1), &callback)
37
+ ret = callback.call
39
38
  ret
40
39
  end
41
40
  end
@@ -43,17 +42,20 @@ module Renee
43
42
 
44
43
  # @private
45
44
  module ClassMethods
46
- def chain_method(*methods)
45
+ def chainable?(m)
46
+ method_defined?(:"#{m}_chainable")
47
+ end
48
+
49
+ def chainable(*methods)
47
50
  methods.each do |m|
48
- class_eval <<-EOT, __FILE__, __LINE__ + 1
49
- alias_method :#{m}_without_chain, :#{m}
50
- def #{m}(*args, &blk)
51
- blk.nil? ? ChainingProxy.new(self, #{m.inspect}, args) : #{m}_without_chain(*args, &blk)
52
- end
53
- private :#{m}_without_chain
54
- EOT
51
+ define_method(:"#{m}_chainable") { }
55
52
  end
56
53
  end
54
+
55
+ end
56
+
57
+ def create_chain_proxy(method_name, *args)
58
+ ChainingProxy.new(self, method_name, args)
57
59
  end
58
60
 
59
61
  def self.included(o)
@@ -5,9 +5,9 @@ module Renee
5
5
 
6
6
  # Exception for attempting to define an env accessor cannot be written as a method name.
7
7
  # @example
8
- # env_accessor "current.user" # raises InvalidEnvName
8
+ # env_accessor "current.user" # raises InvalidEnvNameError
9
9
  # env_accessor "current.user" => :current_user # this works
10
- InvalidEnvName = Class.new(RuntimeError)
10
+ InvalidEnvNameError = Class.new(RuntimeError)
11
11
 
12
12
  # Class-methods included by this module.
13
13
  module ClassMethods
@@ -55,7 +55,7 @@ module Renee
55
55
  yield k, v
56
56
  end
57
57
  else
58
- raise InvalidEnvName, "Called env attr for #{a.inspect}, to use this, call your env method like this. env_reader #{a.inspect} => #{a.to_s.gsub(/-\./, '_').to_sym.inspect}" if a.to_s[/[-\.]/]
58
+ raise InvalidEnvNameError, "Called env attr for #{a.inspect}, to use this, call your env method like this. env_reader #{a.inspect} => #{a.to_s.gsub(/-\./, '_').to_sym.inspect}" if a.to_s[/[-\.]/]
59
59
  yield a, a.to_sym
60
60
  end
61
61
  end
@@ -0,0 +1,31 @@
1
+ module Renee
2
+ class Core
3
+ module Plugins
4
+ attr_reader :init_blocks, :before_blocks, :after_blocks
5
+
6
+ def on_init(&blk)
7
+ init_blocks << blk
8
+ end
9
+
10
+ def init_blocks
11
+ (@init_blocks ||= [])
12
+ end
13
+
14
+ def on_before(&blk)
15
+ before_blocks << blk
16
+ end
17
+
18
+ def before_blocks
19
+ (@before_blocks ||= [])
20
+ end
21
+
22
+ def on_after(&blk)
23
+ before_blocks << blk
24
+ end
25
+
26
+ def after_blocks
27
+ (@after_blocks ||= [])
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ module Renee
2
+ class Core
3
+ module ClassMethods
4
+ def use(mw, *args, &blk)
5
+ middlewares << [mw, args, blk]
6
+ end
7
+
8
+ def middlewares
9
+ @middlewares ||= []
10
+ end
11
+ end
12
+
13
+ # This module deals with the Rack#call compilance. It defines #call and also defines several critical methods
14
+ # used by interaction by other application modules.
15
+ module RequestContext
16
+ attr_reader :env, :request, :detected_extension
17
+
18
+ # Provides a rack interface compliant call method.
19
+ # @param[Hash] env The rack environment.
20
+ def call(e)
21
+ initialize_plugins
22
+ idx = 0
23
+ next_app = proc do |env|
24
+ if idx == self.class.middlewares.size
25
+ @env, @request = env, Rack::Request.new(env)
26
+ @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
27
+ # TODO clear template cache in development? `template_cache.clear`
28
+ out = catch(:halt) do
29
+ begin
30
+ self.class.before_blocks.each { |b| instance_eval(&b) }
31
+ instance_eval(&self.class.application_block)
32
+ rescue ClientError => e
33
+ e.response ? instance_eval(&e.response) : halt("There was an error with your request", 400)
34
+ rescue NotMatchedError => e
35
+ # unmatched, continue on
36
+ end
37
+ Renee::Core::Response.new("Not found", 404).finish
38
+ end
39
+ self.class.after_blocks.each { |a| out = instance_exec(out, &a) }
40
+ out
41
+ else
42
+ middleware = self.class.middlewares[idx]
43
+ idx += 1
44
+ middleware[0].new(next_app, *middleware[1], &middleware[2]).call(env)
45
+ end
46
+ end
47
+ next_app[e]
48
+ end # call
49
+
50
+ def initialize_plugins
51
+ self.class.init_blocks.each { |init_block| self.class.class_eval(&init_block) }
52
+ self.class.send(:define_method, :initialize_plugins) { }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -42,7 +42,9 @@ module Renee
42
42
  # respond("Hello", 200, "foo" => "bar")
43
43
  #
44
44
  def respond(body=[], status=200, header={}, &blk)
45
- Renee::Core::Response.new(body, status, header).tap { |r| r.instance_eval(&blk) if block_given? }.finish
45
+ response = Renee::Core::Response.new(body, status, header)
46
+ response.instance_eval(&blk) if block_given?
47
+ response.finish
46
48
  end
47
49
 
48
50
  ##
@@ -4,6 +4,26 @@ module Renee
4
4
  module Routing
5
5
  include Chaining
6
6
 
7
+ # Allow continued routing if a routing block fails to match
8
+ #
9
+ # @param [Boolean] val
10
+ # indicate if continued routing should be allowed.
11
+ #
12
+ # @api public
13
+ def continue_routing
14
+ if block_given?
15
+ original_env = @env.dup
16
+ begin
17
+ yield
18
+ rescue NotMatchedError
19
+ @env = original_env
20
+ end
21
+ else
22
+ create_chain_proxy(:continue_routing)
23
+ end
24
+ end
25
+ chainable :continue_routing
26
+
7
27
  # Match a path to respond to.
8
28
  #
9
29
  # @param [String] p
@@ -21,27 +41,27 @@ module Renee
21
41
  #
22
42
  # @api public
23
43
  def path(p, &blk)
24
- p = p[1, p.size] if p[0] == ?/
25
- extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
26
- part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/, &blk)
27
- end
28
- chain_method :path
29
-
30
- # Like #path, but requires the entire path to be consumed.
31
- # @see #path
32
- def whole_path(p, &blk)
33
- path(p) { complete(&blk) }
44
+ if blk
45
+ p = p[1, p.size] if p[0] == ?/
46
+ extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
47
+ part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/, &blk)
48
+ else
49
+ create_chain_proxy(:path, p)
50
+ end
34
51
  end
35
- chain_method :whole_path
52
+ chainable :path
36
53
 
37
54
  # Like #path, but doesn't look for leading slashes.
38
- def part(p, &blk)
39
- p = /^\/?#{Regexp.quote(p)}/ if p.is_a?(String)
40
- if match = env['PATH_INFO'][p]
41
- with_path_part(match) { blk.call }
55
+ def part(p)
56
+ if block_given?
57
+ p = /^\/?#{Regexp.quote(p)}/ if p.is_a?(String)
58
+ if match = env['PATH_INFO'][p]
59
+ with_path_part(match) { yield }
60
+ end
61
+ else
62
+ create_chain_proxy(:part, p)
42
63
  end
43
64
  end
44
- chain_method :part
45
65
 
46
66
  # Match parts off the path as variables. The parts matcher can conform to either a regular expression, or be an Integer, or
47
67
  # simply a String.
@@ -68,54 +88,54 @@ module Renee
68
88
  #
69
89
  # @api public
70
90
  def variable(type = nil, &blk)
71
- complex_variable(type, '/', 1, &blk)
91
+ blk ? complex_variable(type, '/', 1, &blk) : create_chain_proxy(:variable, type)
72
92
  end
73
93
  alias_method :var, :variable
74
- chain_method :variable, :var
94
+ chainable :variable, :var
95
+
96
+ def optional_variable(type = nil, &blk)
97
+ blk ? complex_variable(type, '/', 0..1) { |vars| blk[vars.first] } : create_chain_proxy(:variable, type)
98
+ end
99
+ alias_method :optional, :optional_variable
100
+ chainable :optional, :optional_variable
75
101
 
76
102
  # Same as variable except you can match multiple variables with the same type.
77
103
  # @param [Range, Integer] count The number of parameters to capture.
78
104
  # @param [Symbol] type The type to use for match.
79
105
  def multi_variable(count, type = nil, &blk)
80
- complex_variable(type, '/', count, &blk)
106
+ blk ? complex_variable(type, '/', count, &blk) : create_chain_proxy(:multi_variable, count, type)
81
107
  end
82
108
  alias_method :multi_var, :multi_variable
83
109
  alias_method :mvar, :multi_variable
84
- chain_method :multi_variable, :multi_var, :mvar
110
+ chainable :multi_variable, :multi_var, :mvar
85
111
 
86
112
  # Same as variable except it matches indefinitely.
87
113
  # @param [Symbol] type The type to use for match.
88
114
  def repeating_variable(type = nil, &blk)
89
- complex_variable(type, '/', nil, &blk)
115
+ blk ? complex_variable(type, '/', nil, &blk) : create_chain_proxy(:repeating_variable, type)
90
116
  end
91
117
  alias_method :glob, :repeating_variable
92
- chain_method :repeating_variable, :glob
118
+ chainable :repeating_variable, :glob
93
119
 
94
120
  # Match parts off the path as variables without a leading slash.
95
121
  # @see #variable
96
122
  # @api public
97
123
  def partial_variable(type = nil, &blk)
98
- complex_variable(type, nil, 1, &blk)
124
+ blk ? complex_variable(type, nil, 1, &blk) : create_chain_proxy(:partial_variable, type)
99
125
  end
100
126
  alias_method :part_var, :partial_variable
101
- chain_method :partial_variable, :part_var
127
+ chainable :partial_variable, :part_var
102
128
 
103
- # Match an extension.
129
+ # Returns the matched extension. If no extension is present, returns `nil`.
104
130
  #
105
131
  # @example
106
- # extension('html') { |path| halt [200, {}, path] }
132
+ # halt [200, {}, path] if extension == 'html'
107
133
  #
108
134
  # @api public
109
- def extension(ext, &blk)
110
- if detected_extension && match = detected_extension[ext]
111
- if match == detected_extension
112
- (ext_match = env['PATH_INFO'][/\/?\.#{match}/]) ?
113
- with_path_part(ext_match, &blk) : blk.call
114
- end
115
- end
135
+ def extension
136
+ detected_extension
116
137
  end
117
138
  alias_method :ext, :extension
118
- chain_method :extension, :ext
119
139
 
120
140
  # Match no extension.
121
141
  #
@@ -124,9 +144,8 @@ module Renee
124
144
  #
125
145
  # @api public
126
146
  def no_extension(&blk)
127
- blk.call if detected_extension.nil?
147
+ blk.call unless detected_extension
128
148
  end
129
- chain_method :no_extension
130
149
 
131
150
  # Match any remaining path.
132
151
  #
@@ -135,10 +154,10 @@ module Renee
135
154
  #
136
155
  # @api public
137
156
  def remainder(&blk)
138
- with_path_part(env['PATH_INFO']) { |var| blk.call(var) }
157
+ blk ? with_path_part(env['PATH_INFO']) { |var| blk.call(var) } : create_chain_proxy(:remainder)
139
158
  end
140
159
  alias_method :catchall, :remainder
141
- chain_method :remainder, :catchall
160
+ chainable :remainder, :catchall
142
161
 
143
162
  # Respond to a GET request and yield the block.
144
163
  #
@@ -146,10 +165,10 @@ module Renee
146
165
  # get { halt [200, {}, "hello world"] }
147
166
  #
148
167
  # @api public
149
- def get(path = nil, &blk)
150
- request_method('GET', path, &blk)
168
+ def get(&blk)
169
+ blk ? request_method('GET', &blk) : create_chain_proxy(:get)
151
170
  end
152
- chain_method :get
171
+ chainable :get
153
172
 
154
173
  # Respond to a POST request and yield the block.
155
174
  #
@@ -157,10 +176,10 @@ module Renee
157
176
  # post { halt [200, {}, "hello world"] }
158
177
  #
159
178
  # @api public
160
- def post(path = nil, &blk)
161
- request_method('POST', path, &blk)
179
+ def post(&blk)
180
+ blk ? request_method('POST', &blk) : create_chain_proxy(:post)
162
181
  end
163
- chain_method :post
182
+ chainable :post
164
183
 
165
184
  # Respond to a PUT request and yield the block.
166
185
  #
@@ -168,10 +187,10 @@ module Renee
168
187
  # put { halt [200, {}, "hello world"] }
169
188
  #
170
189
  # @api public
171
- def put(path = nil, &blk)
172
- request_method('PUT', path, &blk)
190
+ def put(&blk)
191
+ blk ? request_method('PUT', &blk) : create_chain_proxy(:put)
173
192
  end
174
- chain_method :put
193
+ chainable :put
175
194
 
176
195
  # Respond to a DELETE request and yield the block.
177
196
  #
@@ -179,10 +198,10 @@ module Renee
179
198
  # delete { halt [200, {}, "hello world"] }
180
199
  #
181
200
  # @api public
182
- def delete(path = nil, &blk)
183
- request_method('DELETE', path, &blk)
201
+ def delete(&blk)
202
+ blk ? request_method('DELETE', &blk) : create_chain_proxy(:delete)
184
203
  end
185
- chain_method :delete
204
+ chainable :delete
186
205
 
187
206
  # Match only when the path is either '' or '/'.
188
207
  #
@@ -191,66 +210,42 @@ module Renee
191
210
  #
192
211
  # @api public
193
212
  def complete(&blk)
194
- if env['PATH_INFO'] == '/' or env['PATH_INFO'] == ''
195
- with_path_part(env['PATH_INFO']) { blk.call }
213
+ if blk
214
+ with_path_part(env['PATH_INFO']) { blk.call } if complete?
215
+ else
216
+ create_chain_proxy(:complete)
196
217
  end
197
218
  end
198
- chain_method :complete
219
+ chainable :complete
199
220
 
200
- # Match only when the path is ''.
221
+ # Test if the path has been consumed
201
222
  #
202
223
  # @example
203
- # empty { halt [200, {}, "hello world"] }
224
+ # if complete?
225
+ # halt "Hey, the path is done"
226
+ # end
204
227
  #
205
228
  # @api public
206
- def empty(&blk)
207
- if env['PATH_INFO'] == ''
208
- with_path_part(env['PATH_INFO']) { blk.call }
209
- end
229
+ def complete?
230
+ (detected_extension and env['PATH_INFO'] =~ /^\/?(\.#{Regexp.quote(detected_extension)}\/?)?$/) || (detected_extension.nil? and env['PATH_INFO'] =~ /^\/?$/)
210
231
  end
211
- chain_method :empty
212
232
 
213
- # Match variables within the query string.
214
- #
215
- # @param [Array, Hash] q
216
- # Either an array or hash of things to match query string variables. If given
217
- # an array, if you pass the values for each key as parameters to the block given.
218
- # If given a hash, then every value must be able to be matched by a registered type.
219
- #
220
- # @example
221
- # query(:key => :integer) { |h| halt [200, {}, "hello world #{h[:key]}"] }
233
+ # Match only when the path is ''.
222
234
  #
223
235
  # @example
224
- # query(:key) { |val| halt [200, {}, "key is #{val}"] }
236
+ # empty { halt [200, {}, "hello world"] }
225
237
  #
226
238
  # @api public
227
- def query(q, &blk)
228
- case q
229
- when Hash then blk.call(Hash[q.map{|(k, v)| [k, transform(v, request[k.to_s]) || return]}])
230
- when Array then blk.call(*q.map{|qk| request[qk.to_s] or return })
231
- else query([q], &blk)
239
+ def empty(&blk)
240
+ if blk
241
+ if env['PATH_INFO'] == ''
242
+ with_path_part(env['PATH_INFO']) { blk.call }
243
+ end
244
+ else
245
+ create_chain_proxy(:empty)
232
246
  end
233
247
  end
234
- chain_method :query
235
-
236
- # Yield block if the query string matches.
237
- #
238
- # @param [String] qs
239
- # The query string to match.
240
- #
241
- # @example
242
- # path 'test' do
243
- # query_string 'foo=bar' do
244
- # halt [200, {}, 'matched']
245
- # end
246
- # end
247
- # GET /test?foo=bar #=> 'matched'
248
- #
249
- # @api public
250
- def query_string(qs, &blk)
251
- blk.call if qs === env['QUERY_STRING']
252
- end
253
- chain_method :query_string
248
+ chainable :empty
254
249
 
255
250
  private
256
251
  def complex_variable(type, prefix, count)
@@ -306,17 +301,19 @@ module Renee
306
301
  end
307
302
 
308
303
  def with_path_part(part)
309
- old_path_info, old_script_name = env['PATH_INFO'], env['SCRIPT_NAME']
310
- script_part, env['PATH_INFO'] = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
304
+ script_part = env['PATH_INFO'][0, part.size]
305
+ env['PATH_INFO'] = env['PATH_INFO'].slice(part.size, env['PATH_INFO'].size)
311
306
  env['SCRIPT_NAME'] += script_part
312
307
  yield script_part
313
- env['PATH_INFO'], env['SCRIPT_NAME'] = old_path_info, old_script_name
308
+ raise NotMatchedError
314
309
  end
315
310
 
316
- def request_method(method, path = nil, &blk)
317
- path ? whole_path(path) { blk.call } : complete { blk.call } if env['REQUEST_METHOD'] == method
311
+ def request_method(method)
312
+ if env['REQUEST_METHOD'] == method && complete?
313
+ yield
314
+ raise NotMatchedError
315
+ end
318
316
  end
319
- chain_method :request_method
320
317
  end
321
318
  end
322
319
  end