renee-core 0.3.11 → 0.4.0.pre1

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.
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