renee-core 0.2.0 → 0.3.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/README.md +5 -5
- data/lib/renee_core.rb +91 -0
- data/lib/renee_core/chaining.rb +71 -0
- data/lib/renee_core/env_accessors.rb +72 -0
- data/lib/{renee-core → renee_core}/exceptions.rb +1 -1
- data/lib/{renee-core → renee_core}/matcher.rb +1 -1
- data/lib/renee_core/rack_interaction.rb +50 -0
- data/lib/renee_core/request_context.rb +25 -0
- data/lib/renee_core/responding.rb +110 -0
- data/lib/{renee-core → renee_core}/response.rb +2 -2
- data/lib/renee_core/routing.rb +322 -0
- data/lib/renee_core/transform.rb +18 -0
- data/lib/{renee-core → renee_core}/url_generation.rb +3 -3
- data/lib/renee_core/version.rb +6 -0
- data/renee-core.gemspec +1 -1
- data/test/env_accessors_test.rb +43 -0
- data/test/include_test.rb +1 -1
- data/test/responding_test.rb +1 -1
- data/test/routing_test.rb +5 -5
- data/test/test_helper.rb +1 -1
- data/test/url_generation_test.rb +8 -8
- metadata +19 -18
- data/lib/renee-core.rb +0 -78
- data/lib/renee-core/application.rb +0 -32
- data/lib/renee-core/application/chaining.rb +0 -73
- data/lib/renee-core/application/rack_interaction.rb +0 -45
- data/lib/renee-core/application/request_context.rb +0 -29
- data/lib/renee-core/application/responding.rb +0 -112
- data/lib/renee-core/application/routing.rb +0 -319
- data/lib/renee-core/application/transform.rb +0 -20
- data/lib/renee-core/settings.rb +0 -63
- data/lib/renee-core/version.rb +0 -6
@@ -1,112 +0,0 @@
|
|
1
|
-
class Renee
|
2
|
-
class Core
|
3
|
-
class Application
|
4
|
-
# Collection of useful methods for responding within a {Renee::Core} app.
|
5
|
-
module Responding
|
6
|
-
# Codes used by Symbol lookup in interpret_response.
|
7
|
-
# @example
|
8
|
-
# halt :unauthorized # would return a 401.
|
9
|
-
#
|
10
|
-
HTTP_CODES = {
|
11
|
-
:ok => 200,
|
12
|
-
:created => 201,
|
13
|
-
:accepted => 202,
|
14
|
-
:no_content => 204,
|
15
|
-
:no_content => 204,
|
16
|
-
:bad_request => 400,
|
17
|
-
:unauthorized => 401,
|
18
|
-
:payment_required => 403,
|
19
|
-
:not_found => 404,
|
20
|
-
:method_not_found => 405,
|
21
|
-
:not_acceptable => 406,
|
22
|
-
:gone => 410,
|
23
|
-
:error => 500,
|
24
|
-
:not_implemented => 501}.freeze
|
25
|
-
|
26
|
-
# Halts current processing to the top-level calling Renee application and uses that as a response.
|
27
|
-
# @param [Object...] response The response to use.
|
28
|
-
# @see #interpret_response
|
29
|
-
def halt(*response)
|
30
|
-
throw :halt, interpret_response(response.size == 1 ? response.first : response)
|
31
|
-
end
|
32
|
-
|
33
|
-
##
|
34
|
-
# Creates a response by allowing the response header, body and status to be passed into the block.
|
35
|
-
#
|
36
|
-
# @param [Array] body The contents to return.
|
37
|
-
# @param [Integer] status The status code to return.
|
38
|
-
# @param [Hash] header The headers to return.
|
39
|
-
# @param [Proc] &blk The response options to specify
|
40
|
-
#
|
41
|
-
# @example
|
42
|
-
# respond { status 200; body "Yay!" }
|
43
|
-
# respond("Hello", 200, "foo" => "bar")
|
44
|
-
#
|
45
|
-
def respond(body=[], status=200, header={}, &blk)
|
46
|
-
Renee::Core::Response.new(body, status, header).tap { |r| r.instance_eval(&blk) if block_given? }.finish
|
47
|
-
end
|
48
|
-
|
49
|
-
##
|
50
|
-
# Creates a response by allowing the response header, body and status to be passed into the block.
|
51
|
-
#
|
52
|
-
# @example
|
53
|
-
# respond! { status 200; body "Yay!" }
|
54
|
-
#
|
55
|
-
# @param (see #respond)
|
56
|
-
# @see #respond
|
57
|
-
def respond!(*args, &blk)
|
58
|
-
halt respond(*args, &blk)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Interprets responses returns by #halt.
|
62
|
-
#
|
63
|
-
# * If it is a Symbol, it will be looked up in {HTTP_CODES}.
|
64
|
-
# * If it is a Symbol, it will use Rack::Response to return the value.
|
65
|
-
# * If it is a Symbol, it will either be used as a Rack response or as a body and status code.
|
66
|
-
# * If it is an Integer, it will use Rack::Response to return the status code.
|
67
|
-
# * Otherwise, #to_s will be called on it and it will be treated as a Symbol.
|
68
|
-
#
|
69
|
-
# @param [Object] response This can be either a Symbol, String, Array or any Object.
|
70
|
-
#
|
71
|
-
def interpret_response(response)
|
72
|
-
case response
|
73
|
-
when Array then
|
74
|
-
case response.size
|
75
|
-
when 3 then response
|
76
|
-
when 2 then Renee::Core::Response.new(response[1], HTTP_CODES[response[0]] || response[0]).finish
|
77
|
-
else raise "I don't know how to render #{response.inspect}"
|
78
|
-
end
|
79
|
-
when String then Renee::Core::Response.new(response).finish
|
80
|
-
when Integer then Renee::Core::Response.new("Status code #{response}", response).finish
|
81
|
-
when Symbol then interpret_response(HTTP_CODES[response] || response.to_s)
|
82
|
-
when Proc then instance_eval(&response)
|
83
|
-
else raise "Unable to render #{response.inspect}"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns a rack-based response for redirection.
|
88
|
-
# @param [String] path The URL to redirect to.
|
89
|
-
# @param [Integer] code The HTTP code to use.
|
90
|
-
# @example
|
91
|
-
# r = Renee::Core.new { get { halt redirect '/index' } }
|
92
|
-
# r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
|
93
|
-
def redirect(path, code = 302)
|
94
|
-
response = ::Rack::Response.new
|
95
|
-
response.redirect(path, code)
|
96
|
-
response.finish
|
97
|
-
end
|
98
|
-
|
99
|
-
# Halts with a rack-based response for redirection.
|
100
|
-
# @see #redirect
|
101
|
-
# @param [String] path The URL to redirect to.
|
102
|
-
# @param [Integer] code The HTTP code to use.
|
103
|
-
# @example
|
104
|
-
# r = Renee::Core.new { get { redirect! '/index' } }
|
105
|
-
# r.call(Rack::MockResponse("/")) # => [302, {"Location" => "/index"}, []]
|
106
|
-
def redirect!(path, code = 302)
|
107
|
-
halt redirect(path, code)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
@@ -1,319 +0,0 @@
|
|
1
|
-
class Renee
|
2
|
-
class Core
|
3
|
-
class Application
|
4
|
-
# Collection of useful methods for routing within a {Renee::Core} app.
|
5
|
-
module Routing
|
6
|
-
include Chaining
|
7
|
-
|
8
|
-
# Match a path to respond to.
|
9
|
-
#
|
10
|
-
# @param [String] p
|
11
|
-
# path to match.
|
12
|
-
# @param [Proc] blk
|
13
|
-
# block to yield
|
14
|
-
#
|
15
|
-
# @example
|
16
|
-
# path('/') { ... } #=> '/'
|
17
|
-
# path('test') { ... } #=> '/test'
|
18
|
-
#
|
19
|
-
# path 'foo' do
|
20
|
-
# path('bar') { ... } #=> '/foo/bar'
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# @api public
|
24
|
-
def path(p, &blk)
|
25
|
-
p = p[1, p.size] if p[0] == ?/
|
26
|
-
extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
|
27
|
-
part(/^\/#{Regexp.quote(p)}(\/?|$)(?=\/|$#{extension_part})/, &blk)
|
28
|
-
end
|
29
|
-
chain_method :path
|
30
|
-
|
31
|
-
# Like #path, but requires the entire path to be consumed.
|
32
|
-
# @see #path
|
33
|
-
def whole_path(p, &blk)
|
34
|
-
path(p) { complete(&blk) }
|
35
|
-
end
|
36
|
-
chain_method :whole_path
|
37
|
-
|
38
|
-
# Like #path, but doesn't automatically match trailing-slashes.
|
39
|
-
# @see #path
|
40
|
-
def exact_path(p, &blk)
|
41
|
-
p = p[1, part.size] if p[0] == ?/
|
42
|
-
part(/^\/#{Regexp.quote(p)}/, &blk)
|
43
|
-
end
|
44
|
-
chain_method :exact_path
|
45
|
-
|
46
|
-
# Like #path, but doesn't look for leading slashes.
|
47
|
-
def part(p, &blk)
|
48
|
-
p = /\/?#{Regexp.quote(p)}/ if p.is_a?(String)
|
49
|
-
if match = env['PATH_INFO'][p]
|
50
|
-
with_path_part(match) { blk.call }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
chain_method :part
|
54
|
-
|
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 casting your variable for you.
|
58
|
-
# @param[Object] default the default value to use if your param cannot be successfully matched.
|
59
|
-
#
|
60
|
-
# @example
|
61
|
-
# path '/' do
|
62
|
-
# variable { |id| halt [200, {}, id] }
|
63
|
-
# end
|
64
|
-
# GET /hey #=> [200, {}, 'hey']
|
65
|
-
#
|
66
|
-
# @example
|
67
|
-
# path '/' do
|
68
|
-
# variable(:integer) { |id| halt [200, {}, "This is a numeric id: #{id}"] }
|
69
|
-
# end
|
70
|
-
# GET /123 #=> [200, {}, 'This is a numeric id: 123']
|
71
|
-
#
|
72
|
-
# @example
|
73
|
-
# path '/test' do
|
74
|
-
# variable { |foo, bar| halt [200, {}, "#{foo}-#{bar}"] }
|
75
|
-
# end
|
76
|
-
# GET /test/hey/there #=> [200, {}, 'hey-there']
|
77
|
-
#
|
78
|
-
# @api public
|
79
|
-
def variable(type = nil, &blk)
|
80
|
-
complex_variable(type, '/', 1, &blk)
|
81
|
-
end
|
82
|
-
alias_method :var, :variable
|
83
|
-
chain_method :variable, :var
|
84
|
-
|
85
|
-
# Same as variable except you can match multiple variables with the same type.
|
86
|
-
# @param [Range, Integer] count The number of parameters to capture.
|
87
|
-
# @param [Symbol] type The type to use for match.
|
88
|
-
def multi_variable(count, type = nil, &blk)
|
89
|
-
complex_variable(type, '/', count, &blk)
|
90
|
-
end
|
91
|
-
alias_method :multi_var, :multi_variable
|
92
|
-
alias_method :mvar, :multi_variable
|
93
|
-
chain_method :multi_variable, :multi_var, :mvar
|
94
|
-
|
95
|
-
# Same as variable except it matches indefinitely.
|
96
|
-
# @param [Symbol] type The type to use for match.
|
97
|
-
def repeating_variable(type = nil, &blk)
|
98
|
-
complex_variable(type, '/', nil, &blk)
|
99
|
-
end
|
100
|
-
alias_method :glob, :repeating_variable
|
101
|
-
chain_method :repeating_variable, :glob
|
102
|
-
|
103
|
-
# Match parts off the path as variables without a leading slash.
|
104
|
-
# @see #variable
|
105
|
-
# @api public
|
106
|
-
def partial_variable(type = nil, &blk)
|
107
|
-
complex_variable(type, nil, 1, &blk)
|
108
|
-
end
|
109
|
-
alias_method :part_var, :partial_variable
|
110
|
-
chain_method :partial_variable, :part_var
|
111
|
-
|
112
|
-
# Match an extension.
|
113
|
-
#
|
114
|
-
# @example
|
115
|
-
# extension('html') { |path| halt [200, {}, path] }
|
116
|
-
#
|
117
|
-
# @api public
|
118
|
-
def extension(ext, &blk)
|
119
|
-
if detected_extension && match = detected_extension[ext]
|
120
|
-
if match == detected_extension
|
121
|
-
(ext_match = env['PATH_INFO'][/\/?\.#{match}/]) ?
|
122
|
-
with_path_part(ext_match, &blk) : blk.call
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
alias_method :ext, :extension
|
127
|
-
chain_method :extension, :ext
|
128
|
-
|
129
|
-
# Match no extension.
|
130
|
-
#
|
131
|
-
# @example
|
132
|
-
# no_extension { |path| halt [200, {}, path] }
|
133
|
-
#
|
134
|
-
# @api public
|
135
|
-
def no_extension(&blk)
|
136
|
-
blk.call if detected_extension.nil?
|
137
|
-
end
|
138
|
-
chain_method :no_extension
|
139
|
-
|
140
|
-
# Match any remaining path.
|
141
|
-
#
|
142
|
-
# @example
|
143
|
-
# remainder { |path| halt [200, {}, path] }
|
144
|
-
#
|
145
|
-
# @api public
|
146
|
-
def remainder(&blk)
|
147
|
-
with_path_part(env['PATH_INFO']) { |var| blk.call(var) }
|
148
|
-
end
|
149
|
-
alias_method :catchall, :remainder
|
150
|
-
chain_method :remainder, :catchall
|
151
|
-
|
152
|
-
# Respond to a GET request and yield the block.
|
153
|
-
#
|
154
|
-
# @example
|
155
|
-
# get { halt [200, {}, "hello world"] }
|
156
|
-
#
|
157
|
-
# @api public
|
158
|
-
def get(path = nil, &blk)
|
159
|
-
request_method('GET', path, &blk)
|
160
|
-
end
|
161
|
-
chain_method :get
|
162
|
-
|
163
|
-
# Respond to a POST request and yield the block.
|
164
|
-
#
|
165
|
-
# @example
|
166
|
-
# post { halt [200, {}, "hello world"] }
|
167
|
-
#
|
168
|
-
# @api public
|
169
|
-
def post(path = nil, &blk)
|
170
|
-
request_method('POST', path, &blk)
|
171
|
-
end
|
172
|
-
chain_method :post
|
173
|
-
|
174
|
-
# Respond to a PUT request and yield the block.
|
175
|
-
#
|
176
|
-
# @example
|
177
|
-
# put { halt [200, {}, "hello world"] }
|
178
|
-
#
|
179
|
-
# @api public
|
180
|
-
def put(path = nil, &blk)
|
181
|
-
request_method('PUT', path, &blk)
|
182
|
-
end
|
183
|
-
chain_method :put
|
184
|
-
|
185
|
-
# Respond to a DELETE request and yield the block.
|
186
|
-
#
|
187
|
-
# @example
|
188
|
-
# delete { halt [200, {}, "hello world"] }
|
189
|
-
#
|
190
|
-
# @api public
|
191
|
-
def delete(path = nil, &blk)
|
192
|
-
request_method('DELETE', path, &blk)
|
193
|
-
end
|
194
|
-
chain_method :delete
|
195
|
-
|
196
|
-
# Match only when the path has been completely consumed.
|
197
|
-
#
|
198
|
-
# @example
|
199
|
-
# complete { halt [200, {}, "hello world"] }
|
200
|
-
#
|
201
|
-
# @api public
|
202
|
-
def complete(&blk)
|
203
|
-
if env['PATH_INFO'] == '' || is_index_request
|
204
|
-
with_path_part(env['PATH_INFO']) { blk.call }
|
205
|
-
end
|
206
|
-
end
|
207
|
-
chain_method :complete
|
208
|
-
|
209
|
-
# Match variables within the query string.
|
210
|
-
#
|
211
|
-
# @param [Array, Hash] q
|
212
|
-
# Either an array or hash of things to match query string variables. If given
|
213
|
-
# an array, if you pass the values for each key as parameters to the block given.
|
214
|
-
# If given a hash, then every value must be able to be matched by a registered type.
|
215
|
-
#
|
216
|
-
# @example
|
217
|
-
# query(:key => :integer) { |h| halt [200, {}, "hello world #{h[:key]}"] }
|
218
|
-
#
|
219
|
-
# @example
|
220
|
-
# query(:key) { |val| halt [200, {}, "key is #{val}"] }
|
221
|
-
#
|
222
|
-
# @api public
|
223
|
-
def query(q, &blk)
|
224
|
-
case q
|
225
|
-
when Hash then blk.call(Hash[q.map{|(k, v)| [k, transform(v, request[k.to_s]) || return]}])
|
226
|
-
when Array then blk.call(*q.map{|qk| request[qk.to_s] or return })
|
227
|
-
else query([q], &blk)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
chain_method :query
|
231
|
-
|
232
|
-
# Yield block if the query string matches.
|
233
|
-
#
|
234
|
-
# @param [String] qs
|
235
|
-
# The query string to match.
|
236
|
-
#
|
237
|
-
# @example
|
238
|
-
# path 'test' do
|
239
|
-
# query_string 'foo=bar' do
|
240
|
-
# halt [200, {}, 'matched']
|
241
|
-
# end
|
242
|
-
# end
|
243
|
-
# GET /test?foo=bar #=> 'matched'
|
244
|
-
#
|
245
|
-
# @api public
|
246
|
-
def query_string(qs, &blk)
|
247
|
-
blk.call if qs === env['QUERY_STRING']
|
248
|
-
end
|
249
|
-
chain_method :query_string
|
250
|
-
|
251
|
-
private
|
252
|
-
def complex_variable(type, prefix, count)
|
253
|
-
matcher = variable_matcher_for_type(type)
|
254
|
-
path = env['PATH_INFO'].dup
|
255
|
-
vals = []
|
256
|
-
var_index = 0
|
257
|
-
variable_matching_loop(count) do
|
258
|
-
path.start_with?(prefix) ? path.slice!(0, prefix.size) : break if prefix
|
259
|
-
if match = matcher[path]
|
260
|
-
path.slice!(0, match.first.size)
|
261
|
-
vals << match.last
|
262
|
-
end
|
263
|
-
end
|
264
|
-
return unless count.nil? || count === vals.size
|
265
|
-
with_path_part(env['PATH_INFO'][0, env['PATH_INFO'].size - path.size]) do
|
266
|
-
if count == 1
|
267
|
-
yield(vals.first)
|
268
|
-
else
|
269
|
-
yield(vals)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
def variable_matching_loop(count)
|
275
|
-
case count
|
276
|
-
when Range then count.max.times { break unless yield }
|
277
|
-
when nil then loop { break unless yield }
|
278
|
-
else count.times { break unless yield }
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def variable_matcher_for_type(type)
|
283
|
-
if settings.variable_types.key?(type)
|
284
|
-
settings.variable_types[type]
|
285
|
-
else
|
286
|
-
regexp = case type
|
287
|
-
when nil, String
|
288
|
-
detected_extension ?
|
289
|
-
/(([^\/](?!#{Regexp.quote(detected_extension)}$))+)(?=$|\/|\.#{Regexp.quote(detected_extension)})/ :
|
290
|
-
/([^\/]+)(?=$|\/)/
|
291
|
-
when Regexp
|
292
|
-
type
|
293
|
-
else
|
294
|
-
raise "Unexpected variable type #{type.inspect}"
|
295
|
-
end
|
296
|
-
proc do |path|
|
297
|
-
if match = /^#{regexp.to_s}/.match(path)
|
298
|
-
[match[0]]
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def with_path_part(part)
|
305
|
-
old_path_info, old_script_name = env['PATH_INFO'], env['SCRIPT_NAME']
|
306
|
-
script_part, env['PATH_INFO'] = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
|
307
|
-
env['SCRIPT_NAME'] += script_part
|
308
|
-
yield script_part
|
309
|
-
env['PATH_INFO'], env['SCRIPT_NAME'] = old_path_info, old_script_name
|
310
|
-
end
|
311
|
-
|
312
|
-
def request_method(method, path = nil, &blk)
|
313
|
-
path ? whole_path(path) { blk.call } : complete { blk.call } if env['REQUEST_METHOD'] == method
|
314
|
-
end
|
315
|
-
chain_method :request_method
|
316
|
-
end
|
317
|
-
end
|
318
|
-
end
|
319
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
class Renee
|
2
|
-
class Core
|
3
|
-
class Application
|
4
|
-
# Module used for transforming arbitrary values using the registerd variable types.
|
5
|
-
# @see #register_variable_name.
|
6
|
-
#
|
7
|
-
module Transform
|
8
|
-
# Transforms a value according to the rules specified by #register_variable_name.
|
9
|
-
# @param [Symbol] name The name of the variable type.
|
10
|
-
# @param [String] value The value to transform.
|
11
|
-
# @return The transformed value or nil.
|
12
|
-
def transform(type, value)
|
13
|
-
if settings.variable_types.key?(type) and m = settings.variable_types[type][value]
|
14
|
-
m.first == value ? m.last : nil
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|