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
@@ -0,0 +1,322 @@
|
|
1
|
+
module Renee
|
2
|
+
class Core
|
3
|
+
# Collection of useful methods for routing within a {Renee::Core} app.
|
4
|
+
module Routing
|
5
|
+
include Chaining
|
6
|
+
|
7
|
+
# Match a path to respond to.
|
8
|
+
#
|
9
|
+
# @param [String] p
|
10
|
+
# path to match.
|
11
|
+
# @param [Proc] blk
|
12
|
+
# block to yield
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# path('/') { ... } #=> '/'
|
16
|
+
# path('test') { ... } #=> '/test'
|
17
|
+
#
|
18
|
+
# path 'foo' do
|
19
|
+
# path('bar') { ... } #=> '/foo/bar'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def path(p, &blk)
|
24
|
+
p = p[1, p.size] if p[0] == ?/
|
25
|
+
extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
|
26
|
+
part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/, &blk)
|
27
|
+
end
|
28
|
+
chain_method :path
|
29
|
+
|
30
|
+
# Like #path, but requires the entire path to be consumed.
|
31
|
+
# @see #path
|
32
|
+
def whole_path(p, &blk)
|
33
|
+
path(p) { complete(&blk) }
|
34
|
+
end
|
35
|
+
chain_method :whole_path
|
36
|
+
|
37
|
+
# Like #path, but doesn't look for leading slashes.
|
38
|
+
def part(p, &blk)
|
39
|
+
p = /^\/?#{Regexp.quote(p)}/ if p.is_a?(String)
|
40
|
+
if match = env['PATH_INFO'][p]
|
41
|
+
with_path_part(match) { blk.call }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
chain_method :part
|
45
|
+
|
46
|
+
# Match parts off the path as variables. The parts matcher can conform to either a regular expression, or be an Integer, or
|
47
|
+
# simply a String.
|
48
|
+
# @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.
|
49
|
+
# @param[Object] default the default value to use if your param cannot be successfully matched.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# path '/' do
|
53
|
+
# variable { |id| halt [200, {}, id] }
|
54
|
+
# end
|
55
|
+
# GET /hey #=> [200, {}, 'hey']
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# path '/' do
|
59
|
+
# variable(:integer) { |id| halt [200, {}, "This is a numeric id: #{id}"] }
|
60
|
+
# end
|
61
|
+
# GET /123 #=> [200, {}, 'This is a numeric id: 123']
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# path '/test' do
|
65
|
+
# variable { |foo, bar| halt [200, {}, "#{foo}-#{bar}"] }
|
66
|
+
# end
|
67
|
+
# GET /test/hey/there #=> [200, {}, 'hey-there']
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def variable(type = nil, &blk)
|
71
|
+
complex_variable(type, '/', 1, &blk)
|
72
|
+
end
|
73
|
+
alias_method :var, :variable
|
74
|
+
chain_method :variable, :var
|
75
|
+
|
76
|
+
# Same as variable except you can match multiple variables with the same type.
|
77
|
+
# @param [Range, Integer] count The number of parameters to capture.
|
78
|
+
# @param [Symbol] type The type to use for match.
|
79
|
+
def multi_variable(count, type = nil, &blk)
|
80
|
+
complex_variable(type, '/', count, &blk)
|
81
|
+
end
|
82
|
+
alias_method :multi_var, :multi_variable
|
83
|
+
alias_method :mvar, :multi_variable
|
84
|
+
chain_method :multi_variable, :multi_var, :mvar
|
85
|
+
|
86
|
+
# Same as variable except it matches indefinitely.
|
87
|
+
# @param [Symbol] type The type to use for match.
|
88
|
+
def repeating_variable(type = nil, &blk)
|
89
|
+
complex_variable(type, '/', nil, &blk)
|
90
|
+
end
|
91
|
+
alias_method :glob, :repeating_variable
|
92
|
+
chain_method :repeating_variable, :glob
|
93
|
+
|
94
|
+
# Match parts off the path as variables without a leading slash.
|
95
|
+
# @see #variable
|
96
|
+
# @api public
|
97
|
+
def partial_variable(type = nil, &blk)
|
98
|
+
complex_variable(type, nil, 1, &blk)
|
99
|
+
end
|
100
|
+
alias_method :part_var, :partial_variable
|
101
|
+
chain_method :partial_variable, :part_var
|
102
|
+
|
103
|
+
# Match an extension.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# extension('html') { |path| halt [200, {}, path] }
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def extension(ext, &blk)
|
110
|
+
if detected_extension && match = detected_extension[ext]
|
111
|
+
if match == detected_extension
|
112
|
+
(ext_match = env['PATH_INFO'][/\/?\.#{match}/]) ?
|
113
|
+
with_path_part(ext_match, &blk) : blk.call
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
alias_method :ext, :extension
|
118
|
+
chain_method :extension, :ext
|
119
|
+
|
120
|
+
# Match no extension.
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# no_extension { |path| halt [200, {}, path] }
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def no_extension(&blk)
|
127
|
+
blk.call if detected_extension.nil?
|
128
|
+
end
|
129
|
+
chain_method :no_extension
|
130
|
+
|
131
|
+
# Match any remaining path.
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# remainder { |path| halt [200, {}, path] }
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
def remainder(&blk)
|
138
|
+
with_path_part(env['PATH_INFO']) { |var| blk.call(var) }
|
139
|
+
end
|
140
|
+
alias_method :catchall, :remainder
|
141
|
+
chain_method :remainder, :catchall
|
142
|
+
|
143
|
+
# Respond to a GET request and yield the block.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# get { halt [200, {}, "hello world"] }
|
147
|
+
#
|
148
|
+
# @api public
|
149
|
+
def get(path = nil, &blk)
|
150
|
+
request_method('GET', path, &blk)
|
151
|
+
end
|
152
|
+
chain_method :get
|
153
|
+
|
154
|
+
# Respond to a POST request and yield the block.
|
155
|
+
#
|
156
|
+
# @example
|
157
|
+
# post { halt [200, {}, "hello world"] }
|
158
|
+
#
|
159
|
+
# @api public
|
160
|
+
def post(path = nil, &blk)
|
161
|
+
request_method('POST', path, &blk)
|
162
|
+
end
|
163
|
+
chain_method :post
|
164
|
+
|
165
|
+
# Respond to a PUT request and yield the block.
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# put { halt [200, {}, "hello world"] }
|
169
|
+
#
|
170
|
+
# @api public
|
171
|
+
def put(path = nil, &blk)
|
172
|
+
request_method('PUT', path, &blk)
|
173
|
+
end
|
174
|
+
chain_method :put
|
175
|
+
|
176
|
+
# Respond to a DELETE request and yield the block.
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# delete { halt [200, {}, "hello world"] }
|
180
|
+
#
|
181
|
+
# @api public
|
182
|
+
def delete(path = nil, &blk)
|
183
|
+
request_method('DELETE', path, &blk)
|
184
|
+
end
|
185
|
+
chain_method :delete
|
186
|
+
|
187
|
+
# Match only when the path is either '' or '/'.
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# complete { halt [200, {}, "hello world"] }
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def complete(&blk)
|
194
|
+
if env['PATH_INFO'] == '/' or env['PATH_INFO'] == ''
|
195
|
+
with_path_part(env['PATH_INFO']) { blk.call }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
chain_method :complete
|
199
|
+
|
200
|
+
# Match only when the path is ''.
|
201
|
+
#
|
202
|
+
# @example
|
203
|
+
# empty { halt [200, {}, "hello world"] }
|
204
|
+
#
|
205
|
+
# @api public
|
206
|
+
def empty(&blk)
|
207
|
+
if env['PATH_INFO'] == ''
|
208
|
+
with_path_part(env['PATH_INFO']) { blk.call }
|
209
|
+
end
|
210
|
+
end
|
211
|
+
chain_method :empty
|
212
|
+
|
213
|
+
# Match variables within the query string.
|
214
|
+
#
|
215
|
+
# @param [Array, Hash] q
|
216
|
+
# Either an array or hash of things to match query string variables. If given
|
217
|
+
# an array, if you pass the values for each key as parameters to the block given.
|
218
|
+
# If given a hash, then every value must be able to be matched by a registered type.
|
219
|
+
#
|
220
|
+
# @example
|
221
|
+
# query(:key => :integer) { |h| halt [200, {}, "hello world #{h[:key]}"] }
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# query(:key) { |val| halt [200, {}, "key is #{val}"] }
|
225
|
+
#
|
226
|
+
# @api public
|
227
|
+
def query(q, &blk)
|
228
|
+
case q
|
229
|
+
when Hash then blk.call(Hash[q.map{|(k, v)| [k, transform(v, request[k.to_s]) || return]}])
|
230
|
+
when Array then blk.call(*q.map{|qk| request[qk.to_s] or return })
|
231
|
+
else query([q], &blk)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
chain_method :query
|
235
|
+
|
236
|
+
# Yield block if the query string matches.
|
237
|
+
#
|
238
|
+
# @param [String] qs
|
239
|
+
# The query string to match.
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# path 'test' do
|
243
|
+
# query_string 'foo=bar' do
|
244
|
+
# halt [200, {}, 'matched']
|
245
|
+
# end
|
246
|
+
# end
|
247
|
+
# GET /test?foo=bar #=> 'matched'
|
248
|
+
#
|
249
|
+
# @api public
|
250
|
+
def query_string(qs, &blk)
|
251
|
+
blk.call if qs === env['QUERY_STRING']
|
252
|
+
end
|
253
|
+
chain_method :query_string
|
254
|
+
|
255
|
+
private
|
256
|
+
def complex_variable(type, prefix, count)
|
257
|
+
matcher = variable_matcher_for_type(type)
|
258
|
+
path = env['PATH_INFO'].dup
|
259
|
+
vals = []
|
260
|
+
var_index = 0
|
261
|
+
variable_matching_loop(count) do
|
262
|
+
path.start_with?(prefix) ? path.slice!(0, prefix.size) : break if prefix
|
263
|
+
if match = matcher[path]
|
264
|
+
path.slice!(0, match.first.size)
|
265
|
+
vals << match.last
|
266
|
+
end
|
267
|
+
end
|
268
|
+
return unless count.nil? || count === vals.size
|
269
|
+
with_path_part(env['PATH_INFO'][0, env['PATH_INFO'].size - path.size]) do
|
270
|
+
if count == 1
|
271
|
+
yield(vals.first)
|
272
|
+
else
|
273
|
+
yield(vals)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def variable_matching_loop(count)
|
279
|
+
case count
|
280
|
+
when Range then count.max.times { break unless yield }
|
281
|
+
when nil then loop { break unless yield }
|
282
|
+
else count.times { break unless yield }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def variable_matcher_for_type(type)
|
287
|
+
if self.class.variable_types.key?(type)
|
288
|
+
self.class.variable_types[type]
|
289
|
+
else
|
290
|
+
regexp = case type
|
291
|
+
when nil, String
|
292
|
+
detected_extension ?
|
293
|
+
/(([^\/](?!#{Regexp.quote(detected_extension)}$))+)(?=$|\/|\.#{Regexp.quote(detected_extension)})/ :
|
294
|
+
/([^\/]+)(?=$|\/)/
|
295
|
+
when Regexp
|
296
|
+
type
|
297
|
+
else
|
298
|
+
raise "Unexpected variable type #{type.inspect}"
|
299
|
+
end
|
300
|
+
proc do |path|
|
301
|
+
if match = /^#{regexp.to_s}/.match(path)
|
302
|
+
[match[0]]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def with_path_part(part)
|
309
|
+
old_path_info, old_script_name = env['PATH_INFO'], env['SCRIPT_NAME']
|
310
|
+
script_part, env['PATH_INFO'] = old_path_info[0, part.size], old_path_info[part.size, old_path_info.size]
|
311
|
+
env['SCRIPT_NAME'] += script_part
|
312
|
+
yield script_part
|
313
|
+
env['PATH_INFO'], env['SCRIPT_NAME'] = old_path_info, old_script_name
|
314
|
+
end
|
315
|
+
|
316
|
+
def request_method(method, path = nil, &blk)
|
317
|
+
path ? whole_path(path) { blk.call } : complete { blk.call } if env['REQUEST_METHOD'] == method
|
318
|
+
end
|
319
|
+
chain_method :request_method
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Renee
|
2
|
+
class Core
|
3
|
+
# Module used for transforming arbitrary values using the registerd variable types.
|
4
|
+
# @see #register_variable_name.
|
5
|
+
#
|
6
|
+
module Transform
|
7
|
+
# Transforms a value according to the rules specified by #register_variable_name.
|
8
|
+
# @param [Symbol] name The name of the variable type.
|
9
|
+
# @param [String] value The value to transform.
|
10
|
+
# @return The transformed value or nil.
|
11
|
+
def transform(type, value)
|
12
|
+
if self.class.variable_types.key?(type) and m = self.class.variable_types[type][value]
|
13
|
+
m.first == value ? m.last : nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
|
-
|
3
|
+
module Renee
|
4
4
|
class Core
|
5
5
|
# URL generator for creating paths and URLs within your application.
|
6
6
|
module URLGeneration
|
@@ -62,7 +62,7 @@ class Renee
|
|
62
62
|
generator = url_generators[name]
|
63
63
|
generator ? generator.url(*args) : raise("Generator for #{name} doesn't exist")
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
private
|
67
67
|
def url_generators
|
68
68
|
@url_generators ||= {}
|
@@ -105,4 +105,4 @@ class Renee
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
|
-
end
|
108
|
+
end
|
data/renee-core.gemspec
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../test_helper', __FILE__)
|
4
|
+
|
5
|
+
describe Renee::Core::EnvAccessors do
|
6
|
+
it "should allow accessing the env" do
|
7
|
+
@app = Renee.core {
|
8
|
+
self.test = 'hello'
|
9
|
+
path('test').get do
|
10
|
+
halt "test is #{test}"
|
11
|
+
end
|
12
|
+
}.setup {
|
13
|
+
env_accessor :test
|
14
|
+
}
|
15
|
+
get '/test'
|
16
|
+
assert_equal 200, response.status
|
17
|
+
assert_equal 'test is hello', response.body
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should raise when you try to access weird env keys" do
|
21
|
+
assert_raises(Renee::Core::EnvAccessors::InvalidEnvName) {
|
22
|
+
@app = Renee.core {
|
23
|
+
self.test_test = 'hello'
|
24
|
+
}.setup {
|
25
|
+
env_accessor "test.test"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should allow weird env keys if you map them" do
|
31
|
+
@app = Renee.core {
|
32
|
+
self.test_test = 'hello'
|
33
|
+
path('test').get do
|
34
|
+
halt "test is #{test_test}"
|
35
|
+
end
|
36
|
+
}.setup {
|
37
|
+
env_accessor "test.test" => :test_test
|
38
|
+
}
|
39
|
+
get '/test'
|
40
|
+
assert_equal 200, response.status
|
41
|
+
assert_equal 'test is hello', response.body
|
42
|
+
end
|
43
|
+
end
|
data/test/include_test.rb
CHANGED
@@ -3,7 +3,7 @@ require File.expand_path('../test_helper', __FILE__)
|
|
3
3
|
describe "Route::Settings#include" do
|
4
4
|
it "should allow the inclusion of arbitrary modules" do
|
5
5
|
type = { 'Content-Type' => 'text/plain' }
|
6
|
-
@app = Renee
|
6
|
+
@app = Renee.core {
|
7
7
|
halt :ok if respond_to?(:hi)
|
8
8
|
}.setup {
|
9
9
|
include Module.new { def hi; end }
|