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 +6 -0
- data/README.md +1 -1
- data/Rakefile +6 -0
- data/lib/renee-core/application/chaining.rb +61 -0
- data/lib/renee-core/application/rack_interaction.rb +2 -1
- data/lib/renee-core/application/request_context.rb +8 -1
- data/lib/renee-core/application/responding.rb +3 -13
- data/lib/renee-core/application/routing.rb +131 -61
- data/lib/renee-core/application/transform.rb +13 -0
- data/lib/renee-core/application.rb +8 -4
- data/lib/renee-core/exceptions.rb +11 -0
- data/lib/renee-core/matcher.rb +51 -0
- data/lib/renee-core/settings.rb +15 -1
- data/lib/renee-core/version.rb +1 -2
- data/lib/renee-core.rb +3 -1
- data/test/chaining_test.rb +33 -0
- data/test/routing_test.rb +76 -12
- data/test/variable_type_test.rb +57 -0
- metadata +29 -49
data/.yardopts
ADDED
data/README.md
CHANGED
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
|
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)
|
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
|
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.
|
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
|
-
|
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) {
|
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(
|
63
|
-
|
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(
|
73
|
-
|
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)
|
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
|
-
|
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|
|
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)
|
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)
|
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)
|
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)
|
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
|
-
|
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
|
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 =>
|
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.
|
195
|
-
when Array then
|
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
|
-
|
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(
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
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) {
|
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
|
@@ -1,7 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
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,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
|
data/lib/renee-core/settings.rb
CHANGED
@@ -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
|
data/lib/renee-core/version.rb
CHANGED
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 "
|
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 =>
|
74
|
-
halt
|
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=
|
79
|
+
get '/test?foo=123'
|
80
80
|
assert_equal 200, response.status
|
81
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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(
|
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
|
-
|
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
|
+
date: 2011-10-19 00:00:00 Z
|
21
16
|
dependencies:
|
22
17
|
- !ruby/object:Gem::Dependency
|
23
|
-
|
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
|
-
|
27
|
+
version_requirements: *id001
|
38
28
|
- !ruby/object:Gem::Dependency
|
39
|
-
|
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
|
-
|
38
|
+
version_requirements: *id002
|
54
39
|
- !ruby/object:Gem::Dependency
|
55
|
-
|
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
|
-
|
49
|
+
version_requirements: *id003
|
70
50
|
- !ruby/object:Gem::Dependency
|
71
|
-
|
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
|
-
|
60
|
+
version_requirements: *id004
|
86
61
|
- !ruby/object:Gem::Dependency
|
87
|
-
|
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
|
-
|
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:
|
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:
|
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:
|