renee-core 0.4.0.pre1 → 0.4.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -40,11 +40,11 @@ module Renee
40
40
  # end
41
41
  #
42
42
  # @api public
43
- def path(p, &blk)
44
- if blk
43
+ def path(p)
44
+ if block_given?
45
45
  p = p[1, p.size] if p[0] == ?/
46
46
  extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
47
- part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/, &blk)
47
+ part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/) { yield }
48
48
  else
49
49
  create_chain_proxy(:path, p)
50
50
  end
@@ -87,14 +87,17 @@ module Renee
87
87
  # GET /test/hey/there #=> [200, {}, 'hey-there']
88
88
  #
89
89
  # @api public
90
- def variable(type = nil, &blk)
91
- blk ? complex_variable(type, '/', 1, &blk) : create_chain_proxy(:variable, type)
90
+ def variable(*types)
91
+ block_given? ?
92
+ complex_variable(types, '/', types.empty? ? 1 : types.size) { |*a| yield *a } :
93
+ create_chain_proxy(:variable, *types)
92
94
  end
93
95
  alias_method :var, :variable
94
96
  chainable :variable, :var
95
97
 
96
- def optional_variable(type = nil, &blk)
97
- blk ? complex_variable(type, '/', 0..1) { |vars| blk[vars.first] } : create_chain_proxy(:variable, type)
98
+ def optional_variable(type = nil)
99
+ block_given? ?
100
+ complex_variable(type, '/', 0..1) { |vars| yield vars.first } : create_chain_proxy(:variable, type)
98
101
  end
99
102
  alias_method :optional, :optional_variable
100
103
  chainable :optional, :optional_variable
@@ -102,8 +105,10 @@ module Renee
102
105
  # Same as variable except you can match multiple variables with the same type.
103
106
  # @param [Range, Integer] count The number of parameters to capture.
104
107
  # @param [Symbol] type The type to use for match.
105
- def multi_variable(count, type = nil, &blk)
106
- blk ? complex_variable(type, '/', count, &blk) : create_chain_proxy(:multi_variable, count, type)
108
+ def multi_variable(count, type = nil)
109
+ block_given? ?
110
+ complex_variable(type, '/', count) { |mv| yield mv } :
111
+ create_chain_proxy(:multi_variable, count, type)
107
112
  end
108
113
  alias_method :multi_var, :multi_variable
109
114
  alias_method :mvar, :multi_variable
@@ -111,8 +116,10 @@ module Renee
111
116
 
112
117
  # Same as variable except it matches indefinitely.
113
118
  # @param [Symbol] type The type to use for match.
114
- def repeating_variable(type = nil, &blk)
115
- blk ? complex_variable(type, '/', nil, &blk) : create_chain_proxy(:repeating_variable, type)
119
+ def repeating_variable(type = nil)
120
+ block_given? ?
121
+ complex_variable(type, '/', nil) { |mv| yield mv } :
122
+ create_chain_proxy(:repeating_variable, type)
116
123
  end
117
124
  alias_method :glob, :repeating_variable
118
125
  chainable :repeating_variable, :glob
@@ -120,8 +127,10 @@ module Renee
120
127
  # Match parts off the path as variables without a leading slash.
121
128
  # @see #variable
122
129
  # @api public
123
- def partial_variable(type = nil, &blk)
124
- blk ? complex_variable(type, nil, 1, &blk) : create_chain_proxy(:partial_variable, type)
130
+ def partial_variable(type = nil)
131
+ block_given? ?
132
+ complex_variable(type, nil, 1) { |v| yield v } :
133
+ create_chain_proxy(:partial_variable, type)
125
134
  end
126
135
  alias_method :part_var, :partial_variable
127
136
  chainable :partial_variable, :part_var
@@ -143,8 +152,8 @@ module Renee
143
152
  # no_extension { |path| halt [200, {}, path] }
144
153
  #
145
154
  # @api public
146
- def no_extension(&blk)
147
- blk.call unless detected_extension
155
+ def no_extension
156
+ yield unless detected_extension
148
157
  end
149
158
 
150
159
  # Match any remaining path.
@@ -153,8 +162,10 @@ module Renee
153
162
  # remainder { |path| halt [200, {}, path] }
154
163
  #
155
164
  # @api public
156
- def remainder(&blk)
157
- blk ? with_path_part(env['PATH_INFO']) { |var| blk.call(var) } : create_chain_proxy(:remainder)
165
+ def remainder
166
+ block_given? ?
167
+ with_path_part(env['PATH_INFO']) { |var| yield var } :
168
+ create_chain_proxy(:remainder)
158
169
  end
159
170
  alias_method :catchall, :remainder
160
171
  chainable :remainder, :catchall
@@ -165,8 +176,8 @@ module Renee
165
176
  # get { halt [200, {}, "hello world"] }
166
177
  #
167
178
  # @api public
168
- def get(&blk)
169
- blk ? request_method('GET', &blk) : create_chain_proxy(:get)
179
+ def get
180
+ block_given? ? request_method('GET') { yield } : create_chain_proxy(:get)
170
181
  end
171
182
  chainable :get
172
183
 
@@ -176,8 +187,8 @@ module Renee
176
187
  # post { halt [200, {}, "hello world"] }
177
188
  #
178
189
  # @api public
179
- def post(&blk)
180
- blk ? request_method('POST', &blk) : create_chain_proxy(:post)
190
+ def post
191
+ block_given? ? request_method('POST') { yield } : create_chain_proxy(:get)
181
192
  end
182
193
  chainable :post
183
194
 
@@ -187,19 +198,30 @@ module Renee
187
198
  # put { halt [200, {}, "hello world"] }
188
199
  #
189
200
  # @api public
190
- def put(&blk)
191
- blk ? request_method('PUT', &blk) : create_chain_proxy(:put)
201
+ def put
202
+ block_given? ? request_method('PUT') { yield } : create_chain_proxy(:get)
192
203
  end
193
204
  chainable :put
194
205
 
206
+ # Respond to a PATCH request and yield the block.
207
+ #
208
+ # @example
209
+ # put { halt [200, {}, "hello world"] }
210
+ #
211
+ # @api public
212
+ def patch
213
+ block_given? ? request_method('PATCH') { yield } : create_chain_proxy(:get)
214
+ end
215
+ chainable :patch
216
+
195
217
  # Respond to a DELETE request and yield the block.
196
218
  #
197
219
  # @example
198
220
  # delete { halt [200, {}, "hello world"] }
199
221
  #
200
222
  # @api public
201
- def delete(&blk)
202
- blk ? request_method('DELETE', &blk) : create_chain_proxy(:delete)
223
+ def delete
224
+ block_given? ? request_method('DELETE') { yield } : create_chain_proxy(:get)
203
225
  end
204
226
  chainable :delete
205
227
 
@@ -209,9 +231,9 @@ module Renee
209
231
  # complete { halt [200, {}, "hello world"] }
210
232
  #
211
233
  # @api public
212
- def complete(&blk)
213
- if blk
214
- with_path_part(env['PATH_INFO']) { blk.call } if complete?
234
+ def complete
235
+ if block_given?
236
+ with_path_part(env['PATH_INFO']) { yield } if complete?
215
237
  else
216
238
  create_chain_proxy(:complete)
217
239
  end
@@ -227,7 +249,8 @@ module Renee
227
249
  #
228
250
  # @api public
229
251
  def complete?
230
- (detected_extension and env['PATH_INFO'] =~ /^\/?(\.#{Regexp.quote(detected_extension)}\/?)?$/) || (detected_extension.nil? and env['PATH_INFO'] =~ /^\/?$/)
252
+ (detected_extension and env['PATH_INFO'] =~ /^\/?(\.#{Regexp.quote(detected_extension)}\/?)?$/) ||
253
+ (detected_extension.nil? and env['PATH_INFO'] =~ /^\/?$/)
231
254
  end
232
255
 
233
256
  # Match only when the path is ''.
@@ -236,10 +259,10 @@ module Renee
236
259
  # empty { halt [200, {}, "hello world"] }
237
260
  #
238
261
  # @api public
239
- def empty(&blk)
240
- if blk
262
+ def empty
263
+ if block_given?
241
264
  if env['PATH_INFO'] == ''
242
- with_path_part(env['PATH_INFO']) { blk.call }
265
+ with_path_part(env['PATH_INFO']) { yield }
243
266
  end
244
267
  else
245
268
  create_chain_proxy(:empty)
@@ -249,11 +272,10 @@ module Renee
249
272
 
250
273
  private
251
274
  def complex_variable(type, prefix, count)
252
- matcher = variable_matcher_for_type(type)
253
275
  path = env['PATH_INFO'].dup
254
276
  vals = []
255
- var_index = 0
256
- variable_matching_loop(count) do
277
+ variable_matching_loop(count) do |idx|
278
+ matcher = variable_matcher_for_type(type.respond_to?(:at) ? type.at(idx) : type)
257
279
  path.start_with?(prefix) ? path.slice!(0, prefix.size) : break if prefix
258
280
  if match = matcher[path]
259
281
  path.slice!(0, match.first.size)
@@ -272,9 +294,9 @@ module Renee
272
294
 
273
295
  def variable_matching_loop(count)
274
296
  case count
275
- when Range then count.max.times { break unless yield }
276
- when nil then loop { break unless yield }
277
- else count.times { break unless yield }
297
+ when Range then count.max.times { |i| break unless yield i }
298
+ when nil then i = 0; loop { break unless yield i; i+= 1 }
299
+ else count.times { |i| break unless yield i }
278
300
  end
279
301
  end
280
302
 
@@ -304,14 +326,19 @@ module Renee
304
326
  script_part = env['PATH_INFO'][0, part.size]
305
327
  env['PATH_INFO'] = env['PATH_INFO'].slice(part.size, env['PATH_INFO'].size)
306
328
  env['SCRIPT_NAME'] += script_part
329
+ @requested_http_methods.clear
307
330
  yield script_part
308
331
  raise NotMatchedError
309
332
  end
310
333
 
311
334
  def request_method(method)
312
- if env['REQUEST_METHOD'] == method && complete?
313
- yield
314
- raise NotMatchedError
335
+ if complete?
336
+ if env['REQUEST_METHOD'] == method
337
+ yield
338
+ raise NotMatchedError
339
+ else
340
+ @requested_http_methods << method
341
+ end
315
342
  end
316
343
  end
317
344
  end
@@ -13,6 +13,64 @@ module Renee
13
13
  m.first == value ? m.last : nil
14
14
  end
15
15
  end
16
+
17
+ # Class used for variable matching.
18
+ class Matcher
19
+ attr_accessor :name
20
+
21
+ # @param [Regexp] matcher The regexp matcher to determine what is part of the variable.
22
+ def initialize(matcher)
23
+ @matcher = matcher
24
+ end
25
+
26
+ # Used to specific the error handler if the matcher doesn't match anything. By default, there is no error handler.
27
+ # @yield The block to be executed it fails to match.
28
+ def on_error(&blk)
29
+ @error_handler = blk
30
+ self
31
+ end
32
+
33
+ # Used to transform the value matched.
34
+ # @yield TODO
35
+ def on_transform(&blk)
36
+ @transform_handler = blk
37
+ self
38
+ end
39
+
40
+ # Convienence method to creating halting error handler.
41
+ # @param [Symbol, Integer] error_code The HTTP code to halt with.
42
+ # @see #interpret_response
43
+ def halt_on_error!(error_code = :bad_request)
44
+ on_error { halt error_code }
45
+ self
46
+ end
47
+
48
+ # Matcher for string
49
+ # @param [String] val The value to attempt to match on.
50
+ # @raise [ClientError] If the match fails to match and there is an error handler defined.
51
+ def [](val)
52
+ match = nil
53
+ case @matcher
54
+ when Array
55
+ match = nil
56
+ @matcher.find { |m| match = m[val] }
57
+ else
58
+ if match = /^#{@matcher.to_s}/.match(val)
59
+ match = [match[0]]
60
+ match << @transform_handler.call(match.first) if @transform_handler
61
+ match
62
+ end
63
+ end
64
+ if match
65
+ match
66
+ elsif @error_handler
67
+ raise ClientError.new("There was an error interpreting the value #{val.inspect} for #{name.inspect}", &@error_handler)
68
+ end
69
+ end
70
+ end
71
+
72
+ # Matcher for Integers
73
+ IntegerMatcher = Matcher.new(/\d+/).on_transform{|v| Integer(v)}
16
74
  end
17
75
  end
18
76
  end
@@ -0,0 +1,5 @@
1
+ module Renee
2
+ class Core
3
+ VERSION = '0.4.0.pre2'
4
+ end
5
+ end
data/lib/renee/core.rb CHANGED
@@ -1,25 +1,21 @@
1
1
  require 'rack'
2
2
 
3
- require 'renee/version'
4
- require 'renee/core/matcher'
3
+ require 'renee/core/version'
4
+ require 'renee/core/transform'
5
5
  require 'renee/core/chaining'
6
- require 'renee/core/response'
7
6
  require 'renee/core/exceptions'
8
7
  require 'renee/core/rack_interaction'
9
- require 'renee/core/request_context'
10
- require 'renee/core/transform'
11
8
  require 'renee/core/routing'
12
9
  require 'renee/core/responding'
13
- require 'renee/core/env_accessors'
14
10
  require 'renee/core/plugins'
15
11
 
16
12
  # Top-level Renee constant
17
13
  module Renee
18
14
  # @example
19
15
  # Renee.core { path('/hello') { halt :ok } }
20
- def self.core(&blk)
16
+ def self.core(&app)
21
17
  cls = Class.new(Renee::Core)
22
- cls.app(&blk) if blk
18
+ cls.run(&app) if app
23
19
  cls
24
20
  end
25
21
 
@@ -27,12 +23,12 @@ module Renee
27
23
  # For convience you can also used a method named #Renee
28
24
  # for decalaring new instances.
29
25
  class Core
30
- # Current version of Renee::Core
31
- VERSION = Renee::VERSION
32
-
33
26
  # Error raised if routing fails. Use #continue_routing to continue routing.
34
27
  NotMatchedError = Class.new(RuntimeError)
35
28
 
29
+ # Error raised if respond! or respond is used and no body is set.
30
+ NoResponseSetError = Class.new(RuntimeError)
31
+
36
32
  # Class methods that are included in new instances of {Core}
37
33
  module ClassMethods
38
34
  include Plugins
@@ -46,12 +42,22 @@ module Renee
46
42
  new.call(env)
47
43
  end
48
44
 
45
+ # Specify a middleware to use before calling your application.
46
+ def use(mw, *args, &blk)
47
+ middlewares << [mw, args, blk]
48
+ end
49
+
50
+ # Retreive the list of currently available middlewares.
51
+ def middlewares
52
+ @middlewares ||= []
53
+ end
54
+
49
55
  # Allows you to set the #application_block on your class.
50
56
  # @yield The application block
51
- def app(&app)
57
+ def run(&app)
52
58
  @application_block = app
53
59
  setup do
54
- register_variable_type :integer, IntegerMatcher
60
+ register_variable_type :integer, Transform::IntegerMatcher
55
61
  register_variable_type :int, :integer
56
62
  end
57
63
  end
@@ -73,26 +79,68 @@ module Renee
73
79
  # @return [Renee::Core::Matcher] A matcher
74
80
  def register_variable_type(name, matcher)
75
81
  matcher = case matcher
76
- when Matcher then matcher
77
- when Array then Matcher.new(matcher.map{|m| variable_types[m]})
78
- when Symbol then variable_types[matcher]
79
- else Matcher.new(matcher)
82
+ when Transform::Matcher then matcher
83
+ when Array then Transform::Matcher.new(matcher.map{|m| variable_types[m]})
84
+ when Symbol then variable_types[matcher]
85
+ else Transform::Matcher.new(matcher)
80
86
  end
81
87
  matcher.name = name
82
88
  variable_types[name] = matcher
83
89
  end
84
90
  end
85
91
 
92
+ attr_reader :env, :request, :detected_extension
93
+
94
+ # Provides a rack interface compliant call method.
95
+ # @param[Hash] env The rack environment.
96
+ def call(e)
97
+ initialize_plugins
98
+ idx = 0
99
+ next_app = proc do |env|
100
+ if idx == self.class.middlewares.size
101
+ @requested_http_methods = []
102
+ @env, @request = env, Rack::Request.new(env)
103
+ @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
104
+ # TODO clear template cache in development? `template_cache.clear`
105
+ out = catch(:halt) do
106
+ begin
107
+ self.class.before_blocks.each { |b| instance_eval(&b) }
108
+ instance_eval(&self.class.application_block)
109
+ raise NotMatchedError
110
+ rescue ClientError => e
111
+ e.response ? instance_eval(&e.response) : halt("There was an error with your request", 400)
112
+ rescue NotMatchedError => e
113
+ unless @requested_http_methods.empty?
114
+ throw :halt,
115
+ Rack::Response.new(
116
+ "Method #{request.request_method} unsupported, use #{@requested_http_methods.join(", ")} instead", 405,
117
+ {'Allow' => @requested_http_methods.join(", ")}).finish
118
+ end
119
+ end
120
+ Rack::Response.new("Not found", 404).finish
121
+ end
122
+ self.class.after_blocks.each { |a| out = instance_exec(out, &a) }
123
+ out
124
+ else
125
+ middleware = self.class.middlewares[idx]
126
+ idx += 1
127
+ middleware[0].new(next_app, *middleware[1], &middleware[2]).call(env)
128
+ end
129
+ end
130
+ next_app[e]
131
+ end # call
132
+
133
+ def initialize_plugins
134
+ self.class.init_blocks.each { |init_block| self.class.class_eval(&init_block) }
135
+ self.class.send(:define_method, :initialize_plugins) { }
136
+ end
137
+
86
138
  include Chaining
87
- include RequestContext
88
139
  include Routing
89
140
  include Responding
90
141
  include RackInteraction
91
142
  include Transform
92
- include EnvAccessors
93
143
 
94
- class << self
95
- include ClassMethods
96
- end
144
+ extend ClassMethods
97
145
  end
98
146
  end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "renee/core/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "renee-core"
7
+ s.version = Renee::Core::VERSION
8
+ s.authors = ["Josh Hull", "Nathan Esquenazi", "Arthur Chiu"]
9
+ s.email = ["joshbuddy@gmail.com", "nesquena@gmail.com", "mr.arthur.chiu@gmail.com"]
10
+ s.homepage = "http://reneerb.com"
11
+ s.summary = %q{The super-friendly rack helpers}
12
+ s.description = %q{The super-friendly rack helpers.}
13
+
14
+ s.rubyforge_project = "renee-core"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- test`.split("\n") + ["test/test_helper.rb"]
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_runtime_dependency 'rack', ">= 1.3.0"
21
+
22
+ s.add_development_dependency 'minitest', "~> 2.11.1"
23
+ s.add_development_dependency 'bundler'
24
+ s.add_development_dependency "rack-test", ">= 0.5.0"
25
+ s.add_development_dependency "rake"
26
+ end
@@ -1,7 +1,6 @@
1
1
  require File.expand_path('../test_helper', __FILE__)
2
2
 
3
3
  describe "Route chaining" do
4
-
5
4
  it "should chaining" do
6
5
  type = { 'Content-Type' => 'text/plain' }
7
6
  mock_app do
@@ -20,6 +20,27 @@ describe Renee::Core::Responding do
20
20
  assert_equal 'Status code 404', response.body
21
21
  end
22
22
 
23
+ it "should respond with a 404 if the path isn't matched" do
24
+ mock_app do
25
+ path('hello') do
26
+ halt :ok
27
+ end
28
+ end
29
+ get '/'
30
+ assert_equal 404, response.status
31
+ assert_equal 'Not found', response.body
32
+ end
33
+
34
+ it "should respond with a 405 if the path is matched but the request method isn't" do
35
+ mock_app do
36
+ get {}
37
+ post {}
38
+ end
39
+ put '/'
40
+ assert_equal 405, response.status
41
+ assert_equal 'GET, POST', response.headers['Allow']
42
+ end
43
+
23
44
  it "should render from a string" do
24
45
  mock_app do
25
46
  path('/') { halt "hello!" }
@@ -43,7 +64,7 @@ describe Renee::Core::Responding do
43
64
  path('/') { halt :payment_required, "hello!" }
44
65
  end
45
66
  get '/'
46
- assert_equal 403, response.status
67
+ assert_equal 402, response.status
47
68
  assert_equal 'hello!', response.body
48
69
  end
49
70
 
@@ -92,6 +113,15 @@ describe Renee::Core::Responding do
92
113
  assert_equal "bar", response.headers["foo"]
93
114
  assert_equal "hello!", response.body
94
115
  end # respond
116
+
117
+ it "should raise a NoResponseSetError if you respond without any values" do
118
+ mock_app do
119
+ get do
120
+ respond!
121
+ end
122
+ end
123
+ assert_raises(Renee::Core::NoResponseSetError) { get('/') }
124
+ end # respond
95
125
  end
96
126
 
97
127
  describe "#redirect" do
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ $: << File.expand_path('../../lib', __FILE__)
2
+ require 'renee/core'
1
3
  require 'rubygems'
2
4
  gem 'minitest'
3
5
  require 'minitest/autorun'
@@ -19,7 +19,7 @@ describe Renee::Core::Matcher do
19
19
  halt i.inspect
20
20
  end
21
21
  }.setup {
22
- register_variable_type(:symbol, /[a-z_]+/).on_transform{|v| v.to_sym}.raise_on_error!
22
+ register_variable_type(:symbol, /[a-z_]+/).on_transform{|v| v.to_sym}.halt_on_error!
23
23
  }
24
24
  get '/123'
25
25
  assert_equal 400, response.status