renee-core 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- class Renee
1
+ module Renee
2
2
  class Core
3
3
  # The response object for a Renee request. Inherits from the `Rack#Response` object.
4
4
  class Response < Rack::Response
@@ -75,4 +75,4 @@ class Renee
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -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
- class Renee
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
@@ -0,0 +1,6 @@
1
+ module Renee
2
+ class Core
3
+ # The current version of Renee::Core
4
+ VERSION = "0.3.0"
5
+ end
6
+ end
data/renee-core.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require "renee-core/version"
3
+ require "renee_core/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "renee-core"
@@ -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::Core.new {
6
+ @app = Renee.core {
7
7
  halt :ok if respond_to?(:hi)
8
8
  }.setup {
9
9
  include Module.new { def hi; end }