renee-core 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: