renee-core 0.4.0.pre1 → 0.4.0.pre2

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