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.
@@ -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 }