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 +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:
|