renee-core 0.0.1 → 0.1.0

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.
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --output-dir ../doc/core
2
+ --readme README.md
3
+ --no-private
4
+ --title Renee Core
5
+ --markup markdown
6
+ 'lib/**/*.rb'
data/README.md CHANGED
@@ -104,7 +104,7 @@ and even explicitly cast your variable types:
104
104
 
105
105
  ```ruby
106
106
  path('blog') {
107
- var :type => Integer do |id|
107
+ var :integer do |id|
108
108
  get { halt "path is /blog/#{id} and id is an integer" }
109
109
  end
110
110
  end
data/Rakefile CHANGED
@@ -1,8 +1,14 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rake/testtask'
3
+ require 'yard'
3
4
 
4
5
  Rake::TestTask.new do |t|
5
6
  t.libs.push "lib"
6
7
  t.test_files = FileList[File.expand_path('../test/**/*_test.rb', __FILE__)]
7
8
  t.verbose = true
8
9
  end
10
+
11
+ desc "Generate documentation for the Padrino framework"
12
+ task :doc do
13
+ YARD::CLI::Yardoc.new.run
14
+ end
@@ -0,0 +1,61 @@
1
+ require 'set'
2
+
3
+ class Renee
4
+ class Core
5
+ class Application
6
+ module Chaining
7
+ private
8
+ class ChainingProxy
9
+ def initialize(target, proxy_blk)
10
+ @target, @proxy_blk, @calls = target, proxy_blk, []
11
+ end
12
+
13
+ def method_missing(m, *args, &blk)
14
+ @calls << [m, *args]
15
+ if blk.nil? && @target.class.private_method_defined?(:"#{m}_without_chain")
16
+ self
17
+ else
18
+ ret = nil
19
+ @proxy_blk.call(proc do |*inner_args|
20
+ callback = proc do |*callback_args|
21
+ inner_args.concat(callback_args)
22
+ if @calls.size == 0
23
+ return blk.call(*inner_args) if blk
24
+ else
25
+ call = @calls.shift
26
+ ret = @target.send(call.at(0), *call.at(1), &callback)
27
+ end
28
+ end
29
+ call = @calls.shift
30
+ ret = @target.send(call.at(0), *call.at(1), &callback)
31
+ end)
32
+ ret
33
+ end
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+ def chain_method(*methods)
39
+ methods.each do |m|
40
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
41
+ alias_method :#{m}_without_chain, :#{m}
42
+ def #{m}(*args, &blk)
43
+ chain(blk) { |subblk| #{m}_without_chain(*args, &subblk) }
44
+ end
45
+ private :#{m}_without_chain
46
+ EOT
47
+ end
48
+ end
49
+ end
50
+
51
+ def chain(blk, &proxy)
52
+ blk ? yield(blk) : ChainingProxy.new(self, proxy)
53
+ end
54
+
55
+ def self.included(o)
56
+ o.extend(ClassMethods)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -21,6 +21,7 @@ class Renee
21
21
  # get { halt run proc { |env| Renee::Core::Response.new("Hello!").finish } }
22
22
  #
23
23
  def run(app = nil, &blk)
24
+ raise "You cannot supply both a block and an app" unless app.nil? ^ blk.nil?
24
25
  (app || blk).call(env)
25
26
  end
26
27
 
@@ -31,7 +32,7 @@ class Renee
31
32
  # get { run proc { |env| Renee::Core::Response.new("Hello!").finish } }
32
33
  #
33
34
  def run!(*args)
34
- halt! run(*args)
35
+ halt run(*args)
35
36
  end
36
37
  end
37
38
  end
@@ -14,7 +14,14 @@ class Renee
14
14
  @detected_extension = env['PATH_INFO'][/\.([^\.\/]+)$/, 1]
15
15
  @is_index_request = env['PATH_INFO'][/^\/?$/]
16
16
  # TODO clear template cache in development? `template_cache.clear`
17
- catch(:halt) { instance_eval(&application_block); Renee::Core::Response.new("Not found", 404).finish }
17
+ catch(:halt) do
18
+ begin
19
+ instance_eval(&application_block)
20
+ rescue ClientError => e
21
+ e.response ? instance_eval(&e.response) : halt("There was an error with your request", 400)
22
+ end
23
+ Renee::Core::Response.new("Not found", 404).finish
24
+ end
18
25
  end # call
19
26
  end
20
27
  end
@@ -3,10 +3,9 @@ class Renee
3
3
  class Application
4
4
  # Collection of useful methods for responding within a {Renee::Core} app.
5
5
  module Responding
6
-
7
6
  # Codes used by Symbol lookup in interpret_response.
8
7
  # @example
9
- # halt! :unauthorized # would return a 401.
8
+ # halt :unauthorized # would return a 401.
10
9
  #
11
10
  HTTP_CODES = {
12
11
  :ok => 200,
@@ -24,20 +23,10 @@ class Renee
24
23
  :error => 500,
25
24
  :not_implemented => 501}.freeze
26
25
 
27
- # Halts current processing to the top-level calling Renee application and uses that as a response. This method requies that
28
- # the PATH_INFO be completely consumed.
26
+ # Halts current processing to the top-level calling Renee application and uses that as a response.
29
27
  # @param [Object...] response The response to use.
30
28
  # @see #interpret_response
31
29
  def halt(*response)
32
- raise "PATH_INFO hasn't been entirely consumed, you still have #{env['PATH_INFO'].inspect} left. Try putting a #remainder block around it. " if env['PATH_INFO'] != ''
33
- halt! *response
34
- end
35
-
36
- # Halts current processing to the top-level calling Renee application and uses that as a response. Unlike #halt, this does
37
- # not require the path to be consumed.
38
- # @param [Object...] response The response to use.
39
- # @see #interpret_response
40
- def halt!(*response)
41
30
  throw :halt, interpret_response(response.size == 1 ? response.first : response)
42
31
  end
43
32
 
@@ -90,6 +79,7 @@ class Renee
90
79
  when String then Renee::Core::Response.new(response).finish
91
80
  when Integer then Renee::Core::Response.new("Status code #{response}", response).finish
92
81
  when Symbol then interpret_response(HTTP_CODES[response] || response.to_s)
82
+ when Proc then instance_eval(&response)
93
83
  else interpret_response(response.to_s)
94
84
  end
95
85
  end
@@ -3,6 +3,8 @@ class Renee
3
3
  class Application
4
4
  # Collection of useful methods for routing within a {Renee::Core} app.
5
5
  module Routing
6
+ include Chaining
7
+
6
8
  # Match a path to respond to.
7
9
  #
8
10
  # @param [String] p
@@ -21,14 +23,17 @@ class Renee
21
23
  # @api public
22
24
  def path(p, &blk)
23
25
  p = p[1, p.size] if p[0] == ?/
24
- part(/^\/#{Regexp.quote(p)}(\/?$)?/, &blk)
26
+ extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
27
+ part(/^\/#{Regexp.quote(p)}(\/?|$)(?=\/|$#{extension_part})/, &blk)
25
28
  end
29
+ chain_method :path
26
30
 
27
31
  # Like #path, but requires the entire path to be consumed.
28
32
  # @see #path
29
33
  def whole_path(p, &blk)
30
34
  path(p) { complete(&blk) }
31
35
  end
36
+ chain_method :whole_path
32
37
 
33
38
  # Like #path, but doesn't automatically match trailing-slashes.
34
39
  # @see #path
@@ -36,56 +41,69 @@ class Renee
36
41
  p = p[1, part.size] if p[0] == ?/
37
42
  part(/^\/#{Regexp.quote(p)}/, &blk)
38
43
  end
44
+ chain_method :exact_path
39
45
 
40
46
  # Like #path, doesn't look for leading slashes.
41
- def part(p)
47
+ def part(p, &blk)
42
48
  p = /\/?#{Regexp.quote(p)}/ if p.is_a?(String)
43
49
  if match = env['PATH_INFO'][p]
44
- with_path_part(match) { yield }
50
+ with_path_part(match) { blk.call }
45
51
  end
46
52
  end
53
+ chain_method :part
47
54
 
48
- # Match parts off the path as variables.
55
+ # Match parts off the path as variables. The parts matcher can conform to either a regular expression, or be an Integer, or
56
+ # simply a String.
57
+ # @param[Object] type the type of object to match for. If you supply Integer, this will only match integers in addition to
58
+ # casting your variable for you.
59
+ # @param[Object] default the default value to use if your param cannot be successfully matched.
49
60
  #
50
61
  # @example
51
62
  # path '/' do
52
- # variable { |id| halt [200, {}, id }
63
+ # variable { |id| halt [200, {}, id] }
53
64
  # end
54
65
  # GET /hey #=> [200, {}, 'hey']
55
66
  #
67
+ # @example
68
+ # path '/' do
69
+ # variable(:integer) { |id| halt [200, {}, "This is a numeric id: #{id}"] }
70
+ # end
71
+ # GET /123 #=> [200, {}, 'This is a numeric id: 123']
72
+ #
73
+ # @example
56
74
  # path '/test' do
57
75
  # variable { |foo, bar| halt [200, {}, "#{foo}-#{bar}"] }
58
76
  # end
59
77
  # GET /test/hey/there #=> [200, {}, 'hey-there']
60
78
  #
61
79
  # @api public
62
- def variable(*args, &blk)
63
- args << {} unless args.last.is_a?(Hash)
64
- args.last[:prepend] = '/'
65
- partial_variable(*args, &blk)
80
+ def variable(type = nil, &blk)
81
+ complex_variable(type, '/', 1, &blk)
66
82
  end
67
83
  alias_method :var, :variable
84
+ chain_method :variable, :var
85
+
86
+ def multi_variable(count, type = nil, &blk)
87
+ complex_variable(type, '/', count, &blk)
88
+ end
89
+ alias_method :multi_var, :multi_variable
90
+ alias_method :mvar, :multi_variable
91
+ chain_method :multi_variable, :multi_var, :mvar
92
+
93
+ def repeating_variable(type = nil, &blk)
94
+ complex_variable(type, '/', nil, &blk)
95
+ end
96
+ alias_method :glob, :repeating_variable
97
+ chain_method :repeating_variable, :glob
68
98
 
69
99
  # Match parts off the path as variables without a leading slash.
70
100
  # @see #variable
71
101
  # @api public
72
- def partial_variable(*args, &blk)
73
- opts = args.last.is_a?(Hash) ? args.pop : nil
74
- type = args.first || opts && opts[:type]
75
- prepend = opts && opts[:prepend] || ''
76
- if type == Integer
77
- complex_variable(/#{Regexp.quote(prepend)}(\d+)/, proc{|v| Integer(v)}, &blk)
78
- else case type
79
- when nil
80
- complex_variable(/#{Regexp.quote(prepend)}([^\/]+)/, &blk)
81
- when Regexp
82
- complex_variable(/#{Regexp.quote(prepend)}(#{type.to_s})/, &blk)
83
- else
84
- raise "Unexpected variable type #{type.inspect}"
85
- end
86
- end
102
+ def partial_variable(type = nil, &blk)
103
+ complex_variable(type, nil, 1, &blk)
87
104
  end
88
105
  alias_method :part_var, :partial_variable
106
+ chain_method :partial_variable, :part_var
89
107
 
90
108
  # Match an extension.
91
109
  #
@@ -93,15 +111,16 @@ class Renee
93
111
  # extension('html') { |path| halt [200, {}, path] }
94
112
  #
95
113
  # @api public
96
- def extension(ext)
114
+ def extension(ext, &blk)
97
115
  if detected_extension && match = detected_extension[ext]
98
116
  if match == detected_extension
99
117
  (ext_match = env['PATH_INFO'][/\/?\.#{match}/]) ?
100
- with_path_part(ext_match) { yield } : yield
118
+ with_path_part(ext_match, &blk) : blk.call
101
119
  end
102
120
  end
103
121
  end
104
122
  alias_method :ext, :extension
123
+ chain_method :extension, :ext
105
124
 
106
125
  # Match no extension.
107
126
  #
@@ -109,9 +128,10 @@ class Renee
109
128
  # no_extension { |path| halt [200, {}, path] }
110
129
  #
111
130
  # @api public
112
- def no_extension
113
- yield if detected_extension.nil?
131
+ def no_extension(&blk)
132
+ blk.call if detected_extension.nil?
114
133
  end
134
+ chain_method :no_extension
115
135
 
116
136
  # Match any remaining path.
117
137
  #
@@ -119,10 +139,11 @@ class Renee
119
139
  # remainder { |path| halt [200, {}, path] }
120
140
  #
121
141
  # @api public
122
- def remainder
123
- with_path_part(env['PATH_INFO']) { |var| yield var }
142
+ def remainder(&blk)
143
+ with_path_part(env['PATH_INFO']) { |var| blk.call(var) }
124
144
  end
125
145
  alias_method :catchall, :remainder
146
+ chain_method :remainder, :catchall
126
147
 
127
148
  # Respond to a GET request and yield the block.
128
149
  #
@@ -130,9 +151,10 @@ class Renee
130
151
  # get { halt [200, {}, "hello world"] }
131
152
  #
132
153
  # @api public
133
- def get(path = nil)
134
- request_method('GET', path) { yield }
154
+ def get(path = nil, &blk)
155
+ request_method('GET', path, &blk)
135
156
  end
157
+ chain_method :get
136
158
 
137
159
  # Respond to a POST request and yield the block.
138
160
  #
@@ -140,9 +162,10 @@ class Renee
140
162
  # post { halt [200, {}, "hello world"] }
141
163
  #
142
164
  # @api public
143
- def post(path = nil)
144
- request_method('POST', path) { yield }
165
+ def post(path = nil, &blk)
166
+ request_method('POST', path, &blk)
145
167
  end
168
+ chain_method :post
146
169
 
147
170
  # Respond to a PUT request and yield the block.
148
171
  #
@@ -150,9 +173,10 @@ class Renee
150
173
  # put { halt [200, {}, "hello world"] }
151
174
  #
152
175
  # @api public
153
- def put(path = nil)
154
- request_method('PUT', path) { yield }
176
+ def put(path = nil, &blk)
177
+ request_method('PUT', path, &blk)
155
178
  end
179
+ chain_method :put
156
180
 
157
181
  # Respond to a DELETE request and yield the block.
158
182
  #
@@ -160,9 +184,10 @@ class Renee
160
184
  # delete { halt [200, {}, "hello world"] }
161
185
  #
162
186
  # @api public
163
- def delete(path = nil)
164
- request_method('DELETE', path) { yield }
187
+ def delete(path = nil, &blk)
188
+ request_method('DELETE', path, &blk)
165
189
  end
190
+ chain_method :delete
166
191
 
167
192
  # Match only when the path has been completely consumed.
168
193
  #
@@ -170,20 +195,22 @@ class Renee
170
195
  # delete { halt [200, {}, "hello world"] }
171
196
  #
172
197
  # @api public
173
- def complete
174
- with_path_part(env['PATH_INFO']) { yield } if env['PATH_INFO'] == '' || is_index_request
198
+ def complete(&blk)
199
+ if env['PATH_INFO'] == '' || is_index_request
200
+ with_path_part(env['PATH_INFO']) { blk.call }
201
+ end
175
202
  end
203
+ chain_method :complete
176
204
 
177
205
  # Match variables within the query string.
178
206
  #
179
207
  # @param [Array, Hash] q
180
208
  # Either an array or hash of things to match query string variables. If given
181
209
  # an array, if you pass the values for each key as parameters to the block given.
182
- # If given a hash, then every value must be able to #=== match each value in the query
183
- # parameters for each key in the hash.
210
+ # If given a hash, then every value must be able to be matched by a registered type.
184
211
  #
185
212
  # @example
186
- # query(:key => 'value') { halt [200, {}, "hello world"] }
213
+ # query(:key => :integer) { |h| halt [200, {}, "hello world #{h[:key]}"] }
187
214
  #
188
215
  # @example
189
216
  # query(:key) { |val| halt [200, {}, "key is #{val}"] }
@@ -191,11 +218,12 @@ class Renee
191
218
  # @api public
192
219
  def query(q, &blk)
193
220
  case q
194
- when Hash then q.any? {|k,v| !(v === request[k.to_s]) } ? return : yield
195
- when Array then yield *q.map{|qk| request[qk.to_s] or return }
221
+ when Hash then blk.call(Hash[q.map{|(k, v)| [k, transform(v, request[k.to_s]) || return]}])
222
+ when Array then blk.call(*q.map{|qk| request[qk.to_s] or return })
196
223
  else query([q], &blk)
197
224
  end
198
225
  end
226
+ chain_method :query
199
227
 
200
228
  # Yield block if the query string matches.
201
229
  #
@@ -211,34 +239,76 @@ class Renee
211
239
  # GET /test?foo=bar #=> 'matched'
212
240
  #
213
241
  # @api public
214
- def query_string(qs)
215
- yield if qs === env['QUERY_STRING']
242
+ def query_string(qs, &blk)
243
+ blk.call if qs === env['QUERY_STRING']
216
244
  end
245
+ chain_method :query_string
217
246
 
218
247
  private
219
- def complex_variable(matcher = nil, transformer = nil, &blk)
220
- warn "variable currently isn't taking any parameters" unless blk.arity > 0
221
- if var_value = /^#{(matcher ? matcher.to_s : '\/([^\/]+)') * blk.arity}/.match(env['PATH_INFO'])
222
- vars = var_value.to_a
223
- with_path_part(vars.shift) { blk.call *vars.map{|v| transformer ? transformer[v[0, v.size]] : v[0, v.size]} }
248
+ def complex_variable(type, prefix, count)
249
+ matcher = variable_matcher_for_type(type)
250
+ path = env['PATH_INFO'].dup
251
+ vals = []
252
+ var_index = 0
253
+ variable_matching_loop(count) do
254
+ path.start_with?(prefix) ? path.slice!(0, prefix.size) : break if prefix
255
+ if match = matcher[path]
256
+ path.slice!(0, match.first.size)
257
+ vals << match.last
258
+ end
259
+ end
260
+ return unless count.nil? || count === vals.size
261
+ with_path_part(env['PATH_INFO'][0, env['PATH_INFO'].size - path.size]) do
262
+ if count == 1
263
+ yield(vals.first)
264
+ else
265
+ yield(vals)
266
+ end
267
+ end
268
+ end
269
+
270
+ def variable_matching_loop(count)
271
+ case count
272
+ when Range then count.max.times { break unless yield }
273
+ when nil then loop { break unless yield }
274
+ else count.times { break unless yield }
275
+ end
276
+ end
277
+
278
+ def variable_matcher_for_type(type)
279
+ if settings.variable_types.key?(type)
280
+ settings.variable_types[type]
281
+ else
282
+ regexp = case type
283
+ when nil, String
284
+ detected_extension ?
285
+ /(([^\/](?!#{Regexp.quote(detected_extension)}$))+)(?=$|\/|\.#{Regexp.quote(detected_extension)})/ :
286
+ /([^\/]+)(?=$|\/)/
287
+ when Regexp
288
+ type
289
+ else
290
+ raise "Unexpected variable type #{type.inspect}"
291
+ end
292
+ proc do |path|
293
+ if match = /^#{regexp.to_s}/.match(path)
294
+ [match[0]]
295
+ end
296
+ end
224
297
  end
225
298
  end
226
299
 
227
300
  def with_path_part(part)
228
- old_path_info = env['PATH_INFO']
229
- old_script_name = env['SCRIPT_NAME']
230
- old_path_info[part.size, old_path_info.size - part.size]
231
- script_part, remaining_part = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
301
+ old_path_info, old_script_name = env['PATH_INFO'], env['SCRIPT_NAME']
302
+ script_part, env['PATH_INFO'] = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
232
303
  env['SCRIPT_NAME'] += script_part
233
- env['PATH_INFO'] = remaining_part
234
304
  yield script_part
235
- env['PATH_INFO'] = old_path_info
236
- env['SCRIPT_NAME'] = old_script_name
305
+ env['PATH_INFO'], env['SCRIPT_NAME'] = old_path_info, old_script_name
237
306
  end
238
307
 
239
- def request_method(method, path = nil)
240
- path ? whole_path(path) { yield } : complete { yield } if env['REQUEST_METHOD'] == method
308
+ def request_method(method, path = nil, &blk)
309
+ path ? whole_path(path) { blk.call } : complete { blk.call } if env['REQUEST_METHOD'] == method
241
310
  end
311
+ chain_method :request_method
242
312
  end
243
313
  end
244
314
  end
@@ -0,0 +1,13 @@
1
+ class Renee
2
+ class Core
3
+ class Application
4
+ module Transform
5
+ def transform(type, value)
6
+ if settings.variable_types.key?(type) and m = settings.variable_types[type][value]
7
+ m.first == value ? m.last : nil
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,9 @@
1
- require File.expand_path(File.dirname(__FILE__) + "/application/request_context")
2
- require File.expand_path(File.dirname(__FILE__) + "/application/routing")
3
- require File.expand_path(File.dirname(__FILE__) + "/application/responding")
4
- require File.expand_path(File.dirname(__FILE__) + "/application/rack_interaction")
1
+ require "renee-core/application/request_context"
2
+ require "renee-core/application/chaining"
3
+ require "renee-core/application/routing"
4
+ require "renee-core/application/responding"
5
+ require "renee-core/application/rack_interaction"
6
+ require "renee-core/application/transform"
5
7
 
6
8
  class Renee
7
9
  class Core
@@ -11,10 +13,12 @@ class Renee
11
13
  # It also has methods to interpret arguments to #halt and redirection response helpers.
12
14
  # {RackInteraction} adds methods for interacting with Rack.
13
15
  class Application
16
+ include Chaining
14
17
  include RequestContext
15
18
  include Routing
16
19
  include Responding
17
20
  include RackInteraction
21
+ include Transform
18
22
 
19
23
  attr_reader :application_block, :settings
20
24
 
@@ -0,0 +1,11 @@
1
+ class Renee
2
+ class Core
3
+ class ClientError < StandardError
4
+ attr_reader :response
5
+ def initialize(message, response = nil)
6
+ super(message)
7
+ @response = response
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ class Renee
2
+ class Core
3
+ class Matcher
4
+ attr_accessor :name
5
+
6
+ def initialize(matcher)
7
+ @matcher = matcher
8
+ end
9
+
10
+ def on_error(&blk)
11
+ @error_handler = blk
12
+ self
13
+ end
14
+
15
+ def on_transform(&blk)
16
+ @transform_handler = blk
17
+ self
18
+ end
19
+
20
+ def transform(val)
21
+ val
22
+ end
23
+
24
+ def raise_on_error!(error_code = :bad_request)
25
+ on_error { halt error_code }
26
+ self
27
+ end
28
+
29
+ def [](val)
30
+ match = nil
31
+ case @matcher
32
+ when Array
33
+ match = nil
34
+ @matcher.find { |m| match = m[val] }
35
+ else
36
+ if match = /^#{@matcher.to_s}/.match(val)
37
+ match = [match[0]]
38
+ match << @transform_handler.call(match.first) if @transform_handler
39
+ match
40
+ end
41
+ end
42
+ if match
43
+ match
44
+ elsif @error_handler
45
+ raise ClientError.new("There was an error interpreting the value #{val.inspect} for #{name.inspect}", @error_handler)
46
+ end
47
+ end
48
+ end
49
+ IntegerMatcher = Matcher.new(/\d+/).on_transform{|v| Integer(v)}
50
+ end
51
+ end
@@ -8,9 +8,12 @@ class Renee
8
8
  # Renee::Core.new { ... }.setup { views_path "./views" }
9
9
  #
10
10
  class Settings
11
- attr_reader :includes
11
+ attr_reader :includes, :variable_types
12
12
  def initialize
13
13
  @includes = []
14
+ @variable_types = {}
15
+ register_variable_type :integer, IntegerMatcher
16
+ register_variable_type :int, :integer
14
17
  end
15
18
 
16
19
  # Get or sets the views_path for an application.
@@ -29,6 +32,17 @@ class Renee
29
32
  def include(mod)
30
33
  includes << mod
31
34
  end
35
+
36
+ def register_variable_type(name, matcher)
37
+ matcher = case matcher
38
+ when Matcher then matcher
39
+ when Array then Matcher.new(matcher.map{|m| @variable_types[m]})
40
+ when Symbol then @variable_types[matcher]
41
+ else Matcher.new(matcher)
42
+ end
43
+ matcher.name = name
44
+ @variable_types[name] = matcher
45
+ end
32
46
  end
33
47
  end
34
48
  end
@@ -1,6 +1,5 @@
1
1
  class Renee
2
2
  class Core
3
- # The version for the renee-core gem.
4
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
5
4
  end
6
5
  end
data/lib/renee-core.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'rack'
2
2
  require 'renee-core/version'
3
+ require 'renee-core/matcher'
3
4
  require 'renee-core/settings'
4
5
  require 'renee-core/response'
5
6
  require 'renee-core/application'
6
7
  require 'renee-core/url_generation'
8
+ require 'renee-core/exceptions'
7
9
 
8
10
  # Renee top-level constant
9
11
  class Renee
@@ -51,7 +53,7 @@ class Renee
51
53
  #
52
54
  # @api public
53
55
  def setup(path = nil, &blk)
54
- raise "Must be either path or blk to configure settings" if path && blk
56
+ raise "You cannot supply both an argument and a block to the method." unless path.nil? ^ blk.nil?
55
57
  case path
56
58
  when nil then settings.instance_eval(&blk)
57
59
  when Settings then @settings = path
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Route chaining" do
4
+
5
+ it "should chaining" do
6
+ type = { 'Content-Type' => 'text/plain' }
7
+ mock_app do
8
+ path('/').get { halt [200,type,['foo']] }
9
+ path('bar').put { halt [200,type,['bar']] }
10
+ path('bar').var.put { |id| halt [200,type,[id]] }
11
+ path('bar').var.get.halt { |id| "wow, nice to meet you " }
12
+ end
13
+ get '/'
14
+ assert_equal 200, response.status
15
+ assert_equal 'foo', response.body
16
+ put '/bar'
17
+ assert_equal 200, response.status
18
+ assert_equal 'bar', response.body
19
+ put '/bar/asd'
20
+ assert_equal 200, response.status
21
+ assert_equal 'asd', response.body
22
+ end
23
+
24
+ it "should chain and halt with a non-routing method" do
25
+ type = { 'Content-Type' => 'text/plain' }
26
+ mock_app do
27
+ path('/').get.halt "hi"
28
+ end
29
+ get '/'
30
+ assert_equal 200, response.status
31
+ assert_equal 'hi', response.body
32
+ end
33
+ end
data/test/routing_test.rb CHANGED
@@ -70,15 +70,16 @@ describe Renee::Core::Application::Routing do
70
70
  it "accepts a set of query params (as hash)" do
71
71
  mock_app do
72
72
  path 'test' do
73
- query :foo => "bar" do
74
- halt 200
73
+ query :foo => :integer do |h|
74
+ halt "foo is #{h[:foo].inspect}"
75
75
  end
76
76
  end
77
77
  end
78
78
 
79
- get '/test?foo=bar'
79
+ get '/test?foo=123'
80
80
  assert_equal 200, response.status
81
- get '/test?foo=bar2'
81
+ assert_equal 'foo is 123', response.body
82
+ get '/test?foo=bar'
82
83
  assert_equal 404, response.status
83
84
  end
84
85
 
@@ -140,13 +141,13 @@ describe Renee::Core::Application::Routing do
140
141
  end
141
142
 
142
143
  path 'two' do
143
- variable do |foo, bar|
144
+ var.var do |foo, bar|
144
145
  get { halt [200, type,["#{foo}-#{bar}"]] }
145
146
  end
146
147
  end
147
148
 
148
149
  path 'multi' do
149
- variable do |foo, bar, lol|
150
+ multi_var(3) do |foo, bar, lol|
150
151
  post { halt [200, type,["#{foo}-#{bar}-#{lol}"]] }
151
152
  end
152
153
  end
@@ -167,13 +168,13 @@ describe Renee::Core::Application::Routing do
167
168
  type = { 'Content-Type' => 'text/plain' }
168
169
  mock_app do
169
170
  path 'test' do
170
- var do |id|
171
+ var do |id|
171
172
  path 'moar' do
172
173
  post { halt [200, type, [id]] }
173
174
  end
174
175
 
175
176
  path 'more' do
176
- var do |foo, bar|
177
+ var.var do |foo, bar|
177
178
  get { halt [200, type, ["#{foo}-#{bar}"]] }
178
179
 
179
180
  path 'woo' do
@@ -200,7 +201,7 @@ describe Renee::Core::Application::Routing do
200
201
  type = { 'Content-Type' => 'text/plain' }
201
202
  mock_app do
202
203
  path 'add' do
203
- variable :type => Integer do |a, b|
204
+ var(:integer).var(:integer) do |a, b|
204
205
  halt [200, type, ["#{a + b}"]]
205
206
  end
206
207
  end
@@ -211,11 +212,49 @@ describe Renee::Core::Application::Routing do
211
212
  assert_equal '7', response.body
212
213
  end
213
214
 
215
+ it "allows arbitrary ranges of values" do
216
+ type = { 'Content-Type' => 'text/plain' }
217
+ mock_app do
218
+ path 'add' do
219
+ multi_var(2..5, :integer).get do |numbers|
220
+ halt [200, type, ["Add up to #{numbers.inject(numbers.shift) {|m, i| m += i}}"]]
221
+ end
222
+ end
223
+ end
224
+
225
+ get '/add/3/4'
226
+ assert_equal 200, response.status
227
+ assert_equal 'Add up to 7', response.body
228
+ get '/add/3/4/6/7'
229
+ assert_equal 200, response.status
230
+ assert_equal 'Add up to 20', response.body
231
+ get '/add/3/4/6/7/8/10/2'
232
+ assert_equal 404, response.status
233
+ end
234
+
235
+ it "accepts allows repeating vars" do
236
+ type = { 'Content-Type' => 'text/plain' }
237
+ mock_app do
238
+ path 'add' do
239
+ glob :integer do |numbers|
240
+ halt [200, type, ["Add up to #{numbers.inject(numbers.shift) {|m, i| m += i}}"]]
241
+ end
242
+ end
243
+ end
244
+
245
+ get '/add/3/4'
246
+ assert_equal 200, response.status
247
+ assert_equal 'Add up to 7', response.body
248
+ get '/add/3/4/6/7'
249
+ assert_equal 200, response.status
250
+ assert_equal 'Add up to 20', response.body
251
+ end
252
+
214
253
  it "accepts a regexp" do
215
254
  type = { 'Content-Type' => 'text/plain' }
216
255
  mock_app do
217
256
  path 'add' do
218
- variable /foo|bar/ do |a, b|
257
+ var(/foo|bar/).var(/foo|bar/) do |a, b|
219
258
  halt [200, type, ["#{a + b}"]]
220
259
  end
221
260
  end
@@ -284,6 +323,31 @@ describe Renee::Core::Application::Routing do
284
323
  get '/test.xml'
285
324
  assert_equal 404, response.status
286
325
  end
326
+
327
+ it "should match an extension when there is a non-specific variable before" do
328
+ mock_app do
329
+ var do |id|
330
+ extension 'html' do
331
+ halt "html #{id}"
332
+ end
333
+ extension 'xml' do
334
+ halt "xml #{id}"
335
+ end
336
+ no_extension do
337
+ halt "none #{id}"
338
+ end
339
+ end
340
+ end
341
+ get '/var.html'
342
+ assert_equal 200, response.status
343
+ assert_equal 'html var', response.body
344
+ get '/var.xml'
345
+ assert_equal 200, response.status
346
+ assert_equal 'xml var', response.body
347
+ get '/var'
348
+ assert_equal 200, response.status
349
+ assert_equal 'none var', response.body
350
+ end
287
351
  end
288
352
 
289
353
  describe "with part and part_var" do
@@ -319,7 +383,7 @@ describe Renee::Core::Application::Routing do
319
383
  mock_app do
320
384
  part '/test' do
321
385
  part 'more' do
322
- part_var Integer do |var|
386
+ part_var :integer do |var|
323
387
  path 'test' do
324
388
  halt var.to_s
325
389
  end
@@ -389,7 +453,7 @@ describe Renee::Core::Application::Routing do
389
453
  it "should match parts and partial vars" do
390
454
  mock_app do
391
455
  part('test') {
392
- part_var(Integer) { |id|
456
+ part_var(:integer) { |id|
393
457
  part('more') {
394
458
  halt "the id is #{id}"
395
459
  }
@@ -0,0 +1,57 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe Renee::Core::Matcher do
4
+ it "should transform variables" do
5
+ mock_app {
6
+ var :symbol do |i|
7
+ halt i.inspect
8
+ end
9
+ }.setup {
10
+ register_variable_type(:symbol, /[a-z_]+/).on_transform{|v| v.to_sym}
11
+ }
12
+ get '/test'
13
+ assert_equal ":test", response.body
14
+ end
15
+
16
+ it "should halt on errors if told to" do
17
+ mock_app {
18
+ var :symbol do |i|
19
+ halt i.inspect
20
+ end
21
+ }.setup {
22
+ register_variable_type(:symbol, /[a-z_]+/).on_transform{|v| v.to_sym}.raise_on_error!
23
+ }
24
+ get '/123'
25
+ assert_equal 400, response.status
26
+ end
27
+
28
+ it "should allow custom error handling behaviour on values that don't match" do
29
+ mock_app {
30
+ var :symbol do |i|
31
+ halt i.inspect
32
+ end
33
+ }.setup {
34
+ register_variable_type(:symbol, /[a-z_]+/).on_transform{|v| v.to_sym}.on_error{|v| halt 500 }
35
+ }
36
+ get '/123'
37
+ assert_equal 500, response.status
38
+ end
39
+
40
+ it "should allow composite variable types" do
41
+ mock_app {
42
+ path("plus10").var(:int_or_hex) do |i|
43
+ halt "plus10: #{i + 10}"
44
+ end
45
+ }.setup {
46
+ register_variable_type(:int, /[0-9]+/).on_transform{|v| Integer(v)}
47
+ register_variable_type(:hex, /0x[0-9a-f]+/).on_transform{|v| v.to_i(16)}
48
+ register_variable_type(:int_or_hex, [:hex, :int])
49
+ }
50
+ get '/plus10/123'
51
+ assert_equal 'plus10: 133', response.body
52
+ get '/plus10/0x7b'
53
+ assert_equal 'plus10: 133', response.body
54
+ end
55
+ end
56
+
57
+
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renee-core
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 1
10
- version: 0.0.1
5
+ version: 0.1.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - Josh Hull
@@ -17,88 +12,63 @@ autorequire:
17
12
  bindir: bin
18
13
  cert_chain: []
19
14
 
20
- date: 2011-10-15 00:00:00 Z
15
+ date: 2011-10-19 00:00:00 Z
21
16
  dependencies:
22
17
  - !ruby/object:Gem::Dependency
23
- version_requirements: &id001 !ruby/object:Gem::Requirement
18
+ name: rack
19
+ requirement: &id001 !ruby/object:Gem::Requirement
24
20
  none: false
25
21
  requirements:
26
22
  - - ~>
27
23
  - !ruby/object:Gem::Version
28
- hash: 27
29
- segments:
30
- - 1
31
- - 3
32
- - 0
33
24
  version: 1.3.0
34
- requirement: *id001
35
25
  type: :runtime
36
26
  prerelease: false
37
- name: rack
27
+ version_requirements: *id001
38
28
  - !ruby/object:Gem::Dependency
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
29
+ name: minitest
30
+ requirement: &id002 !ruby/object:Gem::Requirement
40
31
  none: false
41
32
  requirements:
42
33
  - - ~>
43
34
  - !ruby/object:Gem::Version
44
- hash: 21
45
- segments:
46
- - 2
47
- - 6
48
- - 1
49
35
  version: 2.6.1
50
- requirement: *id002
51
36
  type: :development
52
37
  prerelease: false
53
- name: minitest
38
+ version_requirements: *id002
54
39
  - !ruby/object:Gem::Dependency
55
- version_requirements: &id003 !ruby/object:Gem::Requirement
40
+ name: bundler
41
+ requirement: &id003 !ruby/object:Gem::Requirement
56
42
  none: false
57
43
  requirements:
58
44
  - - ~>
59
45
  - !ruby/object:Gem::Version
60
- hash: 3
61
- segments:
62
- - 1
63
- - 0
64
- - 10
65
46
  version: 1.0.10
66
- requirement: *id003
67
47
  type: :development
68
48
  prerelease: false
69
- name: bundler
49
+ version_requirements: *id003
70
50
  - !ruby/object:Gem::Dependency
71
- version_requirements: &id004 !ruby/object:Gem::Requirement
51
+ name: rack-test
52
+ requirement: &id004 !ruby/object:Gem::Requirement
72
53
  none: false
73
54
  requirements:
74
55
  - - ">="
75
56
  - !ruby/object:Gem::Version
76
- hash: 11
77
- segments:
78
- - 0
79
- - 5
80
- - 0
81
57
  version: 0.5.0
82
- requirement: *id004
83
58
  type: :development
84
59
  prerelease: false
85
- name: rack-test
60
+ version_requirements: *id004
86
61
  - !ruby/object:Gem::Dependency
87
- version_requirements: &id005 !ruby/object:Gem::Requirement
62
+ name: rake
63
+ requirement: &id005 !ruby/object:Gem::Requirement
88
64
  none: false
89
65
  requirements:
90
66
  - - "="
91
67
  - !ruby/object:Gem::Version
92
- hash: 49
93
- segments:
94
- - 0
95
- - 8
96
- - 7
97
68
  version: 0.8.7
98
- requirement: *id005
99
69
  type: :development
100
70
  prerelease: false
101
- name: rake
71
+ version_requirements: *id005
102
72
  description: The super-friendly rack helpers.
103
73
  email:
104
74
  - joshbuddy@gmail.com
@@ -111,24 +81,31 @@ extensions: []
111
81
  extra_rdoc_files: []
112
82
 
113
83
  files:
84
+ - .yardopts
114
85
  - README.md
115
86
  - Rakefile
116
87
  - ideal.rb
117
88
  - lib/renee-core.rb
118
89
  - lib/renee-core/application.rb
90
+ - lib/renee-core/application/chaining.rb
119
91
  - lib/renee-core/application/rack_interaction.rb
120
92
  - lib/renee-core/application/request_context.rb
121
93
  - lib/renee-core/application/responding.rb
122
94
  - lib/renee-core/application/routing.rb
95
+ - lib/renee-core/application/transform.rb
96
+ - lib/renee-core/exceptions.rb
97
+ - lib/renee-core/matcher.rb
123
98
  - lib/renee-core/response.rb
124
99
  - lib/renee-core/settings.rb
125
100
  - lib/renee-core/url_generation.rb
126
101
  - lib/renee-core/version.rb
127
102
  - renee-core.gemspec
103
+ - test/chaining_test.rb
128
104
  - test/responding_test.rb
129
105
  - test/routing_test.rb
130
106
  - test/test_helper.rb
131
107
  - test/url_generation_test.rb
108
+ - test/variable_type_test.rb
132
109
  homepage: ""
133
110
  licenses: []
134
111
 
@@ -142,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
119
  requirements:
143
120
  - - ">="
144
121
  - !ruby/object:Gem::Version
145
- hash: 3
122
+ hash: -2644091870970264113
146
123
  segments:
147
124
  - 0
148
125
  version: "0"
@@ -151,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
128
  requirements:
152
129
  - - ">="
153
130
  - !ruby/object:Gem::Version
154
- hash: 3
131
+ hash: -2644091870970264113
155
132
  segments:
156
133
  - 0
157
134
  version: "0"
@@ -163,7 +140,10 @@ signing_key:
163
140
  specification_version: 3
164
141
  summary: The super-friendly rack helpers.
165
142
  test_files:
143
+ - test/chaining_test.rb
166
144
  - test/responding_test.rb
167
145
  - test/routing_test.rb
168
146
  - test/test_helper.rb
169
147
  - test/url_generation_test.rb
148
+ - test/variable_type_test.rb
149
+ has_rdoc: