sinatra-rroute 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38d10acac3a5452cbbce30a6290ea0e381b5a6c0
4
+ data.tar.gz: fae34365023bd7529d861cfb82f35e1eaacd6eba
5
+ SHA512:
6
+ metadata.gz: bff81eca8b56be9a0b66d08197268040e419f5beead89550918ec961946233b9a079472b069bfb28eb39f8b7a46e96bb9de9df5af93d94272f3c225e0f0a3d34
7
+ data.tar.gz: f3acaf70bdf9f67240eae87693cef2535548cd835aac1a87ce289cec3c6c1d9ccad47ff1a1df4a461fb0326377ab73da36ba924d919bf13a1844228d2eaae076
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rroute.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'sinatra'
8
+ gem 'rack/test'
9
+ gem 'rspec'
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 nounch
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Sinatra-rroute
2
+
3
+ Sinatra-rroute provides Rails-like routes for Sinatra. Using one of the `gget`/`ppost`/`ddelete`/... methods, a route is assigned a name, is mapped to a controller-like method and has a mask to be used by the provided `path` helper function. The `path` helper takes the name of a route, a set of keyword-value mappings, applies them to the mask and gives back the route with each keyword replaced by the associated value.
4
+
5
+ Routes can also be namespaced. Namespaces can be nested. There can be multiple namespaces per app.
6
+
7
+ ## Setup
8
+
9
+ 1. Include `sinatra-rroute` in your Gemfile:
10
+
11
+ gem 'sinatra-rroute'
12
+
13
+ 2. Require it in your Sinatra app:
14
+
15
+ require 'sinatra/rroute'
16
+
17
+ 3. Register it in your app:
18
+
19
+ register Sinatra::Rroute
20
+
21
+ ## Usage
22
+
23
+ ### Route Mapping
24
+
25
+ Mapping routes to controller-like methods works by calling one of the sinatra-rroute mapping methods like so:
26
+
27
+ # This will call Sinatra's `get' method behind the scenes.
28
+ gget '/[uU]ser/:name/:age/?' => :user_info, :as =>
29
+ :user, :mask => '/user/:name/:age/'
30
+
31
+ # This will call Sinatra's `post' method behind the scenes.
32
+ ppost '/[uU]ser/new/:name/:age/?' => :create_user, :as =>
33
+ :new_user, :mask => '/user/new/:name/:age/'
34
+
35
+ The above examples will map GET requests for routes matching the RegEx `'/[uU]ser/:name/:age/?'` to the function `user_info` and POST requests to the route matching the RegEx `'/[uU]ser/new/:name/:age/?'` to the function :create_user. The first route can be referenced as `user`, the second one as `new_user`.
36
+
37
+ Here are all the supported mapping methods and their associated HTTP methods (all of them have the same signature, e.g. `gget('/route/regex/?' => :controller_method, :as => :route_name, :mask => '/route/mask/')`):
38
+
39
+
40
+ | Rroute Method | HTTP Method |
41
+ |---------------+-------------|
42
+ | gget | GET |
43
+ | ppost | POST |
44
+ | pput | PUT |
45
+ | ppatch | PATCH |
46
+ | hhead | HEAD |
47
+ | ddelete | DELETE |
48
+ | ooptions | OPTIONS |
49
+ | llink | LINK |
50
+ | uunlink | UNLINK |
51
+ | ttrace | TRACE |
52
+ | cconnect | CONNECT |
53
+
54
+ *The validity of some of those HTTP methods may be debatable, but it is nice to have them, nonetheless, in case you might need them one day.*
55
+
56
+ If you do not want to map routes to methods, but still give them names and masks, you can use the `mmap` function in combination with Sinatra's built-in `get`/`post`/`delete`/... functions:
57
+
58
+ # Signature: mmap(regex, mask, name)
59
+ get mmap('/user/:name/?', '/user/:name/', :user_info) do
60
+ # ...
61
+ end
62
+
63
+ ### Namespacing
64
+
65
+ Sinatra-rroute comes with a `nnamespace` method which takes the name of a namespace and a block. Each route specified using one of the sinatra-rroute mapping functions (`gget`/`ppost`/`ddelete`/, also `mmap`) inside the block will be prefixed by the specified namespace prefix. Sinatra's built-in `get`/`post`/`delete`/... functions can be used within a namespace block, but are *not* affected by the namespacing. Namespaces can be nested. *Prefixes do have to specify leading and/or trailing slashes explicitely!*
66
+
67
+ Here is an example:
68
+
69
+ nnamespace '/api' do
70
+ nnamespace '/v1' do
71
+ gget '/[uU]ser/:name/:age/?' => :user_info, :as =>
72
+ :user, :mask => '/user/:name/:age/'
73
+
74
+ ppost '/[uU]ser/new/:name/:age/?' => :create_user, :as =>
75
+ :new_user, :mask => '/user/new/:name/:age/'
76
+ end
77
+
78
+ gget '/documentation/?' => :api_documentation, :as =>
79
+ :api_docs, :mask => '/documentation/'
80
+ end
81
+
82
+
83
+ ### Route Helper
84
+
85
+ Sinatra-rroute comes with a `path` helper function. It takes the name of a route and a hash of key-value mappings and returns the path for that route according to its mask. Each "token" in a mask will be replaced by the value for the specified key in the mapping.
86
+
87
+ # Specify a route mapping.
88
+ gget '/[uU]ser/:name/:age/?' => :user_info, :as =>
89
+ :user, :mask => '/user/:name/:age/'
90
+
91
+ # Redirect to this route.
92
+ get '/friend/:name/:age/?' do
93
+ redirect to(path(:user, :name => params[:name], :age => params[:age]))
94
+ end
95
+
96
+ # Generate a link for the route in the view.
97
+ <a href="<%= path(:user, :name => 'John', :age => 32) %>">Get user info</a>
98
+ # This will render the following link:
99
+ # <a href="/user/John/32/">Get user info</a>
100
+
101
+ The helper can be used in your view files as well as in your controller/model/application. It is handy for links, redirecting etc.
102
+
103
+ ### Accessing Routes
104
+
105
+ The full hash of all route-method mappings is attached to your Sinatra app's `settings` object available through the `settings.app_paths` variable.
106
+
107
+ *Note*: The full list of all routes is only available after the last call of one of the sinatra-rroute mapping methods, of course.
108
+
109
+ *Note*: When mixing sinatra-rroute mapping methods (`gget`/`ppost`/`delete`/`mmap`/...) and Sinatra's built-in `get`/`post`/`delete`/... the routes defined using the Sinatra built-ins will *not* show up in `settings.app_paths`!
110
+
111
+ ## Mixing With Sinatra and Other Extensions
112
+
113
+ Sinatra's built-in `get`/`post`/`delete`/... functions as well as other route-related extensions do work fine in combination with sinatra-rroute as long as they do not
114
+
115
+ - clash with sinatra-rroute's function names,
116
+ - fiddle with the `settings.app_paths` variable of your Sinatra app or
117
+ - redefine/overload Sinatra's built-in `get`/`post`/`delete` functions without letting sinatra-rroute know.
118
+
119
+ ## *What about the weird method names?*
120
+
121
+ The names are
122
+
123
+ - concise (as short as possible),
124
+ - consistent (same name generatiion scheme) and
125
+ - unobtrusive (they do not interfere with or overload Sinatra's and Rack's built-in routing methods, they also do not interfere with other Sinatra extensions).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+
5
+ desc 'Run RSpec tests'
6
+ RSpec::Core::RakeTask.new(:test) do |t|
7
+ t.rspec_opts = '-c'
8
+ end
9
+
10
+ task :default do
11
+ puts <<-STRINGSTRINGSTRING
12
+ Run `rake -T' to see a list of all available tasks.
13
+ STRINGSTRINGSTRING
14
+ end
@@ -0,0 +1,431 @@
1
+ module Sinatra
2
+ module Rroute
3
+ private
4
+ def self.registered(app)
5
+ app.set :app_paths, {}
6
+ app.set :app_prefixes, []
7
+ app.helpers do
8
+ # @!visibility public
9
+ #
10
+ # Return a path for a given route mask.
11
+ #
12
+ # @param [String] name The name of a route for which a mask has
13
+ # been specified.
14
+ # @param [Hash{Symbol=>Object}] options Hash of Symbol tokens to be
15
+ # replaced by the associated value. The token (symbol) has to
16
+ # have the exact name specified in the mask for this route.
17
+ #
18
+ # @example
19
+ #
20
+ # # The mask for this route has been defined like this:
21
+ # # gget '/[uU]ser/:name/:age/?' => :user_info, :as =>
22
+ # # user, :mask => '/user/:name/:age/'
23
+ # path :user, :name => 'John', :age => 32
24
+ # # => '/user/John/32/'
25
+ #
26
+ #
27
+ # @return [String] Path for the specified route. Every token for
28
+ # this route is replaced by the specified value in `options'.
29
+ def path(name, *options)
30
+ keywords = options[0]
31
+ # Take the last string as path mask.
32
+ # (Important: Make a duplicate. Do not reference the original
33
+ # mask/regex in `settings.app_paths'!)
34
+ path = settings.app_paths[name.to_sym][:mask].dup ||
35
+ settings.app_paths[name.to_sym][:regex].dup
36
+ if keywords != nil
37
+ keywords.each do |keyword, value|
38
+ path.gsub! /:#{keyword.to_s}/, value.to_s
39
+ end
40
+ end
41
+ path
42
+ end
43
+ end
44
+ end
45
+
46
+ public
47
+
48
+ # @!visibility public
49
+ #
50
+ # Merge paths into `settings.app_paths' which holds all paths defined
51
+ # for the app.
52
+ #
53
+ # @param [Hash{Symbol=>Hash}] paths Paths to be merged into
54
+ # `settings.app_paths'. The hash has to have the structure outlined
55
+ # below.
56
+ #
57
+ # @example
58
+ #
59
+ # paths({:route =>
60
+ # {:http_method =>:get,
61
+ # :regex =>"/route/:name/?",
62
+ # :controller =>nil,
63
+ # :mask =>"/route/:name"},
64
+ # :color =>
65
+ # {:http_method =>:get,
66
+ # :regex =>"/api/new/color/:name/:value/?",
67
+ # :controller =>:show_color,
68
+ # :mask =>"/api/new/color/:name/:value"},
69
+ # :blue =>
70
+ # {:http_method =>:post,
71
+ # :regex =>"/important/api/blue/:name/:value/?",
72
+ # :controller =>:post_blue,
73
+ # :mask =>"/important/api/blue/:name/:value"},
74
+ # :something =>
75
+ # {:http_method =>:get,
76
+ # :regex =>"/something/:name/:value/?",
77
+ # :controller =>:api_something,
78
+ # :mask =>"/something/:name/:value"}})
79
+ #
80
+ # @return [nil] Nothing.
81
+ def paths(paths)
82
+ settings.app_paths.merge! paths
83
+ generate_paths
84
+ end
85
+
86
+
87
+ # @!visibility public
88
+ #
89
+ # Define a route mapping a path to a controller with a HTTP method, a
90
+ # name and a mask for route generation.
91
+ #
92
+ # @param [Hash{String=>Symbol}] mapping Mapping of a route RegEx to a
93
+ # controller function.
94
+ # @param [Symbol] http_method The HTTP method used for this route
95
+ # (`:get', `post', `patch', ...)
96
+ # @param [Hash] options Optional: Options for this mapping. Allowed
97
+ # values: `:as => :route_name', `:mask => "/mask/for/route/:value/"'
98
+ #
99
+ # @example
100
+ #
101
+ # ppath({'/[Pp]ost?/info/:id/?' => :post_info}, :get, :as =>
102
+ # :post, :mask => '/post/:info/')
103
+ #
104
+ # @return [nil] Nothing.
105
+ def ppath(mapping, http_method, *options)
106
+ options = options[0]
107
+ prefix = settings.app_prefixes.empty? ? '' :
108
+ settings.app_prefixes.join
109
+ controller = nil
110
+ if mapping.values[0] != nil
111
+ controller = mapping.values[0].to_sym
112
+ end
113
+ if mapping.keys[0].class == Regexp
114
+ mapping_regex = mapping.keys[0]
115
+ else
116
+ mapping_regex = Regexp.new(mapping.keys[0])
117
+ end
118
+ settings.app_paths.merge!({options[:as].to_sym => {
119
+ :http_method => http_method || :get,
120
+ :regex => %r{#{prefix}\
121
+ #{mapping_regex.source}} || Regexp.new(''),
122
+ :controller => controller,
123
+ :mask => prefix + options[:mask]}}) ||
124
+ ''
125
+ generate_paths
126
+ end
127
+
128
+ # @!visibility public
129
+ # @!method gget(mapping, *options)
130
+ #
131
+ # Map a request using the HTTP GET method to a controller method.
132
+ #
133
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
134
+ # function.
135
+ # @param [Hash] options Optional: Options for this route. Allowed
136
+ # values: `:as => :route_name',
137
+ # `:mask => "/mask/for/route/:value/"'
138
+ #
139
+ # @example
140
+ #
141
+ # gget '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
142
+ # :user, :mask => '/user/:name/:args/'
143
+ #
144
+ # @return [nil] Nothing.
145
+
146
+ # @!visibility public
147
+ # @!method ppost(mapping, *options)
148
+ #
149
+ # Map a request using the HTTP POST method to a controller method.
150
+ #
151
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
152
+ # function.
153
+ # @param [Hash] options Optional: Options for this route. Allowed
154
+ # values: `:as => :route_name',
155
+ # `:mask => "/mask/for/route/:value/"'
156
+ #
157
+ # @example
158
+ #
159
+ # ppost '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
160
+ # :user, :mask => '/user/:name/:args/'
161
+ #
162
+ # @return [nil] Nothing.
163
+
164
+ # @!visibility public
165
+ # @!method pput(mapping, *options)
166
+ #
167
+ # Map a request using the HTTP PUT method to a controller method.
168
+ #
169
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
170
+ # function.
171
+ # @param [Hash] options Optional: Options for this route. Allowed
172
+ # values: `:as => :route_name',
173
+ # `:mask => "/mask/for/route/:value/"'
174
+ #
175
+ # @example
176
+ #
177
+ # pput '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
178
+ # :user, :mask => '/user/:name/:args/'
179
+ #
180
+ # @return [nil] Nothing.
181
+
182
+ # @!visibility public
183
+ # @!method ppatch(mapping, *options)
184
+ #
185
+ # Map a request using the HTTP PATCH method to a controller method.
186
+ #
187
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
188
+ # function.
189
+ # @param [Hash] options Optional: Options for this route. Allowed
190
+ # values: `:as => :route_name',
191
+ # `:mask => "/mask/for/route/:value/"'
192
+ #
193
+ # @example
194
+ #
195
+ # ppatch '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
196
+ # :user, :mask => '/user/:name/:args/'
197
+ #
198
+ # @return [nil] Nothing.
199
+
200
+ # @!visibility public
201
+ # @!method hhead(mapping, *options)
202
+ #
203
+ # Map a request using the HTTP HEAD method to a controller method.
204
+ #
205
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
206
+ # function.
207
+ # @param [Hash] options Optional: Options for this route. Allowed
208
+ # values: `:as => :route_name',
209
+ # `:mask => "/mask/for/route/:value/"'
210
+ #
211
+ # @example
212
+ #
213
+ # hhead '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
214
+ # :user, :mask => '/user/:name/:args/'
215
+ #
216
+ # @return [nil] Nothing.
217
+
218
+ # @!visibility public
219
+ # @!method ddelete(mapping, *options)
220
+ #
221
+ # Map a request using the HTTP DELETE method to a controller method.
222
+ #
223
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
224
+ # function.
225
+ # @param [Hash] options Optional: Options for this route. Allowed
226
+ # values: `:as => :route_name',
227
+ # `:mask => "/mask/for/route/:value/"'
228
+ #
229
+ # @example
230
+ #
231
+ # ddelete '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
232
+ # :user, :mask => '/user/:name/:args/'
233
+ #
234
+ # @return [nil] Nothing.
235
+
236
+ # @!visibility public
237
+ # @!method ooptions(mapping, *options)
238
+ #
239
+ # Map a request using the HTTP OPTIONS method to a controller method.
240
+ #
241
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
242
+ # function.
243
+ # @param [Hash] options Optional: Options for this route. Allowed
244
+ # values: `:as => :route_name',
245
+ # `:mask => "/mask/for/route/:value/"'
246
+ #
247
+ # @example
248
+ #
249
+ # ooptions '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
250
+ # :user, :mask => '/user/:name/:args/'
251
+ #
252
+ # @return [nil] Nothing.
253
+
254
+ # @!visibility public
255
+ # @!method llink(mapping, *options)
256
+ #
257
+ # Map a request using the HTTP LINK method to a controller method.
258
+ #
259
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
260
+ # function.
261
+ # @param [Hash] options Optional: Options for this route. Allowed
262
+ # values: `:as => :route_name',
263
+ # `:mask => "/mask/for/route/:value/"'
264
+ #
265
+ # @example
266
+ #
267
+ # llink '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
268
+ # :user, :mask => '/user/:name/:args/'
269
+ #
270
+ # @return [nil] Nothing.
271
+
272
+ # @!visibility public
273
+ # @!method uunlink(mapping, *options)
274
+ #
275
+ # Map a request using the HTTP UNLINK method to a controller method.
276
+ #
277
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
278
+ # function.
279
+ # @param [Hash] options Optional: Options for this route. Allowed
280
+ # values: `:as => :route_name',
281
+ # `:mask => "/mask/for/route/:value/"'
282
+ #
283
+ # @example
284
+ #
285
+ # uunlink '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
286
+ # :user, :mask => '/user/:name/:args/'
287
+ #
288
+ # @return [nil] Nothing.
289
+
290
+ # @!visibility public
291
+ # @!method ttrace(mapping, *options)
292
+ #
293
+ # Map a request using the HTTP TRACE method to a controller method.
294
+ #
295
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
296
+ # function.
297
+ # @param [Hash] options Optional: Options for this route. Allowed
298
+ # values: `:as => :route_name',
299
+ # `:mask => "/mask/for/route/:value/"'
300
+ #
301
+ # @example
302
+ #
303
+ # ttrace '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
304
+ # :user, :mask => '/user/:name/:args/'
305
+ #
306
+ # @return [nil] Nothing.
307
+
308
+ # @!visibility public
309
+ # @!method cconnect(mapping, *options)
310
+ #
311
+ # Map a request using the HTTP CONNECT method to a controller method.
312
+ #
313
+ # @param [Hash] mapping Mapping from a route RegEx to a controller
314
+ # function.
315
+ # @param [Hash] options Optional: Options for this route. Allowed
316
+ # values: `:as => :route_name',
317
+ # `:mask => "/mask/for/route/:value/"'
318
+ #
319
+ # @example
320
+ #
321
+ # cconnect '/[Uu]sers?/:name/:age/?' => :user_info, :as =>
322
+ # :user, :mask => '/user/:name/:args/'
323
+ #
324
+ # @return [nil] Nothing.
325
+
326
+ {:gget => :get,
327
+ :ppost => :post,
328
+ :pput => :put,
329
+ :ppatch => :patch,
330
+ :hhead => :head,
331
+ :ddelete => :delete,
332
+ :ooptions => :options,
333
+ :llink => :link,
334
+ :uunlink => :unlink,
335
+ :ttrace => :trace,
336
+ :cconnect => :connect}.each do |method_name, http_method|
337
+ define_method method_name do |options|
338
+ # Do some `magic'/hacking to get the same `get' route drawer method
339
+ # interface that Rails has. People are used to it/get to get used
340
+ # to it quickly.
341
+ key = (options.keys - [:as, :regex, :controller, :mask])[0]
342
+ ppath({key => options[key]}, http_method, options)
343
+ end
344
+ end
345
+
346
+ # @!visibility public
347
+ #
348
+ # Map a route RegEx to a controller method with a name and a mask for
349
+ # path generation.
350
+ #
351
+ # Since this method is meant to be used together with Sinatra's
352
+ # built-in `get'/`post'/`patch'/... mapper methods, it does not to
353
+ # map the route to a controller function since this would bypass
354
+ # Sinatra's built-in mapper method.
355
+ #
356
+ # @param [String] regex Route RegEx.
357
+ # @param [String] mask Mask for route generation.
358
+ # @name [Symbol] name Name to reference this route for route generation
359
+ # etc.
360
+ #
361
+ # @example
362
+ #
363
+ # get mmap('/[Uu]ser/:name/:age/?', '/user/:name/:age/', :user) do
364
+ # # ...
365
+ # end
366
+ #
367
+ # @return [nil] Nothing.
368
+ def mmap(regex, mask, name)
369
+ options = {}
370
+ options[:regex] = regex
371
+ options[:mask] = mask
372
+ options[:as] = name
373
+ options[:controller] = nil
374
+ ppath({regex => nil}, nil, options)
375
+
376
+ # Prepend the prefix to the returned RegExp.
377
+ mapping_regex = regex == Regexp ? regex : Regexp.new(regex)
378
+ prefix = settings.app_prefixes.empty? ? '' :
379
+ settings.app_prefixes.join
380
+ output = %r{#{prefix}#{mapping_regex.source}}.source ||
381
+ Regexp.new('')
382
+ # Return the right type of object depending on the input.
383
+ regex.class == Regexp ? Regexp.new(output) : output
384
+ end
385
+
386
+ # @!visibility public
387
+ #
388
+ # Generate all route mappings from the paths in `settings.app_paths'.
389
+ #
390
+ # @return [nil] Nothing.
391
+ def generate_paths
392
+ settings.app_paths.each do |name, value|
393
+ if value[:controller] != nil
394
+ # Paths merged using `paths' should be allowed to to be specified
395
+ # as strings. So they have to be converted here.
396
+ if value[:regex].class != Regexp
397
+ value[:regex] = Regexp.new(value[:regex])
398
+ end
399
+ # Adapt the RegEx representation.
400
+ value[:regex] = value[:regex].source
401
+ send(value[:http_method],
402
+ value[:regex]) { send value[:controller] }
403
+ end
404
+ end
405
+ end
406
+
407
+ # @!visibility public
408
+ #
409
+ # Namespace a all routes defined within the given block using the
410
+ # mapping methods `gget'/`ppost'/`ppatch'/...
411
+ #
412
+ # Sinatra's built-in mapping methods `get'/`post'/`patch' are NOT
413
+ # affected by the namespacing.
414
+ #
415
+ # @param [String] prefix Namespace to be used. HAS to include leading
416
+ # and/or trailing slashes!
417
+ # @yield [] Block containing route mapping method calls
418
+ # (`gget'/`ppost'/`ppatch'/...) and/or other relevant application
419
+ # code. Can also include calls to Sinatra's built-in mapping methods
420
+ # (`get'/`post'/`patch'/...) which will not be affected by the
421
+ # namespacing.
422
+ #
423
+ # @return [nil] Nothing.
424
+ def nnamespace(prefix, &block)
425
+ settings.app_prefixes << prefix
426
+ block.call
427
+ settings.app_prefixes.pop
428
+ end
429
+ end
430
+ register Rroute
431
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Rroute
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
data/rroute.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sinatra/rroute/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sinatra-rroute"
8
+ spec.version = Sinatra::Rroute::VERSION
9
+ spec.authors = ["nounch"]
10
+ spec.email = ["nounch@outlook.com"]
11
+ spec.summary =
12
+ "Rails-style routes with names, namespaces and `path' helper."
13
+ spec.description = <<-DESCRIPTION
14
+ Sinatra-rraoute provides `gget'/`ppost'/`ddelete'/... methods which work
15
+ just like Sinatra's built-in `get'/`post'/`delete'/... methods, but which
16
+ map named routes to functions so that they can be referenced in redirects
17
+ etc.
18
+
19
+ The `path' helper will return a route for a certain route name and the
20
+ given values for this route and comes in handy in both, the
21
+ controller/model component of the application, and the view where you can
22
+ use it to render links, assets URLs, AJAX calls...
23
+
24
+ The nestable `nnamespace' method is useful for API versioning and does not
25
+ interfere with other namespace extensions for Sinatra.
26
+ DESCRIPTION
27
+ spec.homepage = ""
28
+ spec.license = "MIT"
29
+
30
+ spec.files = `git ls-files -z`.split("\x0")
31
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
32
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_development_dependency "bundler", "~> 1.6"
36
+ spec.add_development_dependency "rake"
37
+ end
@@ -0,0 +1,216 @@
1
+ require 'rspec'
2
+ require 'rack/test'
3
+ require 'sinatra/base'
4
+ require_relative '../lib/sinatra/rroute'
5
+ require_relative 'test_app'
6
+
7
+
8
+ RSpec.configure do |config|
9
+ config.include Rack::Test::Methods
10
+ end
11
+
12
+ describe "Rroute" do
13
+ # Setup
14
+
15
+ def app
16
+ TestApp
17
+ end
18
+
19
+ before(:each) do
20
+ @user = { :name => 'John', :age => 32}
21
+ end
22
+
23
+
24
+ # Tests
25
+
26
+
27
+ describe "#paths" do
28
+ it "merges route mappings into `settings.app_paths' uppon a GET \
29
+ request" do
30
+ get '/paths/randomname/randomdescription/'
31
+ paths = {:paths_test_path =>
32
+ {:http_method=>:get,
33
+ :regex =>"/paths/:name/:description/?",
34
+ :controller =>:paths_test,
35
+ :mask =>"/paths/:name/:description/"},
36
+ :route =>
37
+ {:http_method =>:get,
38
+ :regex =>"/route/:name/?",
39
+ :controller =>nil,
40
+ :mask =>"/route/:name"},
41
+ :color =>
42
+ {:http_method =>:get,
43
+ :regex =>"/api/new/color/:name/:value/?",
44
+ :controller =>:show_color,
45
+ :mask =>"/api/new/color/:name/:value"},
46
+ :blue =>
47
+ {:http_method =>:post,
48
+ :regex =>"/important/api/blue/:name/:value/?",
49
+ :controller =>:post_blue,
50
+ :mask =>"/important/api/blue/:name/:value"},
51
+ :something =>
52
+ {:http_method =>:get,
53
+ :regex =>"/something/:name/:value/?",
54
+ :controller =>:api_something,
55
+ :mask =>"/something/:name/:value"}}
56
+ expect(last_response).to be_ok
57
+ expect(last_response.body).to eq(paths.inspect)
58
+ end
59
+ end
60
+
61
+
62
+ describe "#gget" do
63
+ it "successfully dispatches a GET request and defines a route mask" do
64
+ get "/user/#{@user[:name]}/#{@user[:age]}/"
65
+ expect(last_response).to be_ok
66
+ expect(app.settings.app_paths[:user][:mask]).to eq("/user/:name/:age\
67
+ /")
68
+ expect(last_response.body).to eq("/user/#{@user[:name]}/\
69
+ #{@user[:age]}/")
70
+ end
71
+ end
72
+
73
+ describe "#gget (2)" do
74
+ it "successfully dispatches a GET request and defines a route mask
75
+ (using a RegExp-quoted string as route)" do
76
+ get "/regex/#{@user[:name]}/#{@user[:age]}/"
77
+ expect(last_response).to be_ok
78
+ expect(last_response.body).to eq("/regex/#{@user[:name]}/\
79
+ #{@user[:age]}/")
80
+ end
81
+ end
82
+
83
+ describe "#ppost" do
84
+ it "successfully dispatches a POST request and defines a route mask" do
85
+ post "/user/new/#{@user[:name]}/#{@user[:age]}/"
86
+ expect(last_response).to be_ok
87
+ expect(app.settings.app_paths[:new_user][:mask]).to eq("/user/new/\
88
+ :name/:age/")
89
+ expect(last_response.body).to eq("/user/new/#{@user[:name]}/\
90
+ #{@user[:age]}/")
91
+ end
92
+ end
93
+
94
+ describe "#pput" do
95
+ it "successfully dispatches a PUT request and defines a route mask" do
96
+ put "/user/change/#{@user[:name]}/#{@user[:age]}/"
97
+ expect(last_response).to be_ok
98
+ expect(app.settings.app_paths[:change_user][:mask]).to eq("/user/\
99
+ change/:name/:age/")
100
+ expect(last_response.body).to eq("/user/change/#{@user[:name]}/\
101
+ #{@user[:age]}/")
102
+ end
103
+ end
104
+
105
+ describe "#ddelete" do
106
+ it "successfully dispatches a DELETE request and defines a route \
107
+ mask" do
108
+ delete "/user/delete/#{@user[:name]}/#{@user[:age]}/"
109
+ expect(last_response).to be_ok
110
+ expect(app.settings.app_paths[:delete_user][:mask]).to eq("/user/\
111
+ delete/:name/:age/")
112
+ expect(last_response.body).to eq("/user/delete/#{@user[:name]}/\
113
+ #{@user[:age]}/")
114
+ end
115
+ end
116
+
117
+ describe "#hhead" do
118
+ it "successfully dispatches a HEAD request and defines a route \
119
+ mask" do
120
+ head "/user/head/#{@user[:name]}/#{@user[:age]}/"
121
+ expect(last_response).to be_ok
122
+ expect(app.settings.app_paths[:head_user][:mask]).to eq("/user/\
123
+ head/:name/:age/")
124
+ expect(last_response.body).to be_empty
125
+ end
126
+ end
127
+
128
+ describe "#ppath" do
129
+ it "successfully dispatches a GET request and defines a route \
130
+ mask (using `ppath')" do
131
+ get "/user/moreinfo/#{@user[:name]}/#{@user[:age]}/"
132
+ expect(last_response).to be_ok
133
+ expect(app.settings.app_paths[:more_user_info][:mask]).to eq("/user/\
134
+ moreinfo/:name/:age/")
135
+ expect(last_response.body).to eq("/user/moreinfo/#{@user[:name]}/\
136
+ #{@user[:age]}/")
137
+ end
138
+ end
139
+
140
+ describe "#nnampespace" do
141
+ it "successfully dispatches a GET request and defines a route \
142
+ mask (using `nnamespace' and `gget')" do
143
+ # /api/v1/beta
144
+ get "/api/v1/beta/user/#{@user[:name]}/#{@user[:age]}/"
145
+ expect(last_response).to be_ok
146
+ expect(app.settings.app_paths[:api_user_beta][:mask]).to eq("/api/\
147
+ v1/beta/user/:name/:age/")
148
+ expect(last_response.body).to eq("/api/v1/beta/user/#{@user[:name]}/\
149
+ #{@user[:age]}/")
150
+
151
+ # /api/v1/alpha
152
+ get "/api/v1/alpha/user/#{@user[:name]}/#{@user[:age]}/"
153
+ expect(last_response).to be_ok
154
+ expect(app.settings.app_paths[:api_user_alpha][:mask]).to eq("/api/\
155
+ v1/alpha/user/:name/:age/")
156
+ expect(last_response.body).to eq("/api/v1/alpha/user/\
157
+ #{@user[:name]}/#{@user[:age]}/")
158
+
159
+ # `mmap' in a `nnamespace' block
160
+ get "/api/v1/overview/#{@user[:name]}/#{@user[:age]}/"
161
+ expect(last_response).to be_ok
162
+ expect(app.settings.app_paths[:api_overview][:mask]).to eq("/api/\
163
+ v1/overview/:name/:age/")
164
+ expect(last_response.body).to eq("/api/v1/overview/\
165
+ #{@user[:name]}/#{@user[:age]}/")
166
+ end
167
+ end
168
+
169
+ describe "#nnampespace (2)" do
170
+ it "successfully dispatches a GET request and defines a route mask \
171
+ (using `nnamespace' and Sinatra's `get', `post', `put' and `delete')" do
172
+ # GET
173
+ get "/user/sinatra/get/#{@user[:name]}/#{@user[:age]}/"
174
+ expect(last_response).to be_ok
175
+ expect(last_response.body).to eq('User GET')
176
+ # POST
177
+ post "/user/sinatra/post/#{@user[:name]}/#{@user[:age]}/"
178
+ expect(last_response).to be_ok
179
+ expect(last_response.body).to eq('User POST')
180
+ end
181
+ end
182
+
183
+ describe "#mmap" do
184
+ it "successfully dispatches a GET request and defines a route \
185
+ mask (using `mmap' and Sinatra's built-in `get')" do
186
+ get "/user/mmap/#{@user[:name]}/#{@user[:age]}/"
187
+ expect(last_response).to be_ok
188
+ expect(app.settings.app_paths[:mmap_user][:mask]).to eq("/user/mmap/\
189
+ :name/:age/")
190
+ expect(last_response.body).to eq("/user/mmap/#{@user[:name]}/\
191
+ #{@user[:age]}/")
192
+ end
193
+ end
194
+
195
+ describe "#mmap (2)" do
196
+ it "successfully dispatches a GET request and defines a route \
197
+ mask (using `mmap', Sinatra's built-in `get' and a quoted string as route)" do
198
+ get "/user/mmap/regex/#{@user[:name]}/#{@user[:age]}/"
199
+ expect(last_response).to be_ok
200
+ expect(app.settings.app_paths[:mmap_user][:mask]).to eq("/user/mmap/\
201
+ :name/:age/")
202
+ expect(last_response.body).to eq("/user/mmap/regex/#{@user[:name]}/\
203
+ #{@user[:age]}/")
204
+ end
205
+ end
206
+
207
+ describe "#generate_paths" do
208
+ it "successfully dispatches a GET request and generates \
209
+ `settings.app_paths')" do
210
+ get '/generate/paths/'
211
+ expect(last_response).to be_ok
212
+ expect(last_response.body).to eq 'Paths have been generated.'
213
+ expect(app.settings.app_paths).not_to be(nil)
214
+ end
215
+ end
216
+ end
data/spec/test_app.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'sinatra'
2
+ require_relative '../lib/sinatra/rroute'
3
+
4
+ class TestApp < Sinatra::Base
5
+ register Sinatra::Rroute
6
+
7
+ # `paths'
8
+
9
+ gget '/paths/:name/:description/?' => :paths_test, :as =>
10
+ :paths_test_path, :mask => '/paths/:name/:description/'
11
+
12
+ paths({:route =>
13
+ {:http_method =>:get,
14
+ :regex =>"/route/:name/?",
15
+ :controller =>nil,
16
+ :mask =>"/route/:name"},
17
+ :color =>
18
+ {:http_method =>:get,
19
+ :regex =>"/api/new/color/:name/:value/?",
20
+ :controller =>:show_color,
21
+ :mask =>"/api/new/color/:name/:value"},
22
+ :blue =>
23
+ {:http_method =>:post,
24
+ :regex =>"/important/api/blue/:name/:value/?",
25
+ :controller =>:post_blue,
26
+ :mask =>"/important/api/blue/:name/:value"},
27
+ :something =>
28
+ {:http_method =>:get,
29
+ :regex =>"/something/:name/:value/?",
30
+ :controller =>:api_something,
31
+ :mask =>"/something/:name/:value"}})
32
+ @@merged_paths = settings.app_paths.dup
33
+
34
+ def paths_test
35
+ @@merged_paths.inspect
36
+ end
37
+
38
+ def show_color
39
+ '#FF0000'
40
+ end
41
+
42
+ def post_blue
43
+ 'This is a blue post.'
44
+ end
45
+
46
+ def api_something
47
+ 'API...'
48
+ end
49
+
50
+ #########################################################################
51
+ # NOTE
52
+ #
53
+ # For the tests to work correctly do not define more routes above this
54
+ # point in the file or modify `settings.app_paths' in any other way
55
+ # without also adjusting the `paths' variable in the `#paths' RSpec block
56
+ # in `rroute_integration_spec.rb'!
57
+ #########################################################################
58
+
59
+
60
+ # GET
61
+
62
+ gget '/user/:name/:age/?' => :do_show_user_info, :as =>
63
+ :user, :mask => '/user/:name/:age/'
64
+
65
+ def do_show_user_info
66
+ path :user, :name => params[:name], :age => params[:age]
67
+ end
68
+
69
+ # GET (using a RegExp-quoted string as route)
70
+
71
+ gget %r{/regex/:name/:age/?} => :show_regex, :as =>
72
+ :regex, :mask => '/regex/:name/:age/'
73
+
74
+ def show_regex
75
+ path :regex, :name => params[:name], :age => params[:age]
76
+ end
77
+
78
+ # POST
79
+
80
+ ppost '/user/new/:name/:age/?' => :do_create_new_user, :as =>
81
+ :new_user, :mask => '/user/new/:name/:age/'
82
+
83
+ def do_create_new_user
84
+ path :new_user, :name => params[:name], :age => params[:age]
85
+ end
86
+
87
+ # PUT
88
+
89
+ pput '/user/change/:name/:age/?' => :do_change_user, :as =>
90
+ :change_user, :mask => '/user/change/:name/:age/'
91
+
92
+ def do_change_user
93
+ path :change_user, :name => params[:name], :age => params[:age]
94
+ end
95
+
96
+ # DELETE
97
+
98
+ ddelete '/user/delete/:name/:age/?' => :do_delete_user, :as =>
99
+ :delete_user, :mask => '/user/delete/:name/:age/'
100
+
101
+ def do_delete_user
102
+ path :delete_user, :name => params[:name], :age => params[:age]
103
+ end
104
+
105
+ # HEAD
106
+
107
+ hhead '/user/head/:name/:age/?' => :do_head_user, :as =>
108
+ :head_user, :mask => '/user/head/:name/:age/'
109
+
110
+ def do_head_user
111
+ # Because this is a controller method for a HEAD request, the following
112
+ # response should NOT be sent! This is default Sinatra behavior.
113
+ path :head_user, :name => params[:name], :age => params[:age]
114
+ end
115
+
116
+ # GET (using `ppath')
117
+
118
+ ppath({'/user/moreinfo/:name/:age/?' =>
119
+ :show_more_user_info}, :get, :as => :more_user_info, :mask =>
120
+ '/user/moreinfo/:name/:age/')
121
+
122
+ def show_more_user_info
123
+ path :more_user_info, :name => params[:name], :age => params[:age]
124
+ end
125
+
126
+ # GET (using `nnamespace' and `gget')
127
+
128
+ nnamespace '/api' do
129
+ nnamespace '/v1' do
130
+ nnamespace '/beta' do
131
+ gget '/user/:name/:age/?' => :api_do_show_user_info_beta, :as =>
132
+ :api_user_beta, :mask => '/user/:name/:age/'
133
+
134
+ def api_do_show_user_info_beta
135
+ path :api_user_beta, :name => params[:name], :age => params[:age]
136
+ end
137
+ end
138
+
139
+ nnamespace '/alpha' do
140
+ gget '/user/:name/:age/?' => :api_do_show_user_info_alpha, :as =>
141
+ :api_user_alpha, :mask => '/user/:name/:age/'
142
+
143
+ def api_do_show_user_info_alpha
144
+ path :api_user_alpha, :name => params[:name], :age =>
145
+ params[:age]
146
+ end
147
+ end
148
+
149
+ get mmap('/overview/:name/:age/?', '/overview/:name/:age/',
150
+ :api_overview) do
151
+ path :api_overview, :name => params[:name], :age => params[:age]
152
+ end
153
+ end
154
+ end
155
+
156
+ nnamespace '/api' do
157
+ nnamespace '/v1' do
158
+ nnamespace '/beta' do
159
+ get '/user/sinatra/get/:name/:age/?' do
160
+ 'User GET'
161
+ end
162
+
163
+ post '/user/sinatra/post/:name/:age/?' do
164
+ 'User POST'
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ # GET (using `mmap' and Sinatra's built-in `get')
171
+
172
+ get mmap('/user/mmap/:name/:age/?', '/user/mmap/:name/:age/',
173
+ :mmap_user) do
174
+ path :mmap_user, :name => params[:name], :age => params[:age]
175
+ end
176
+
177
+ # GET (using `mmap', Sinatra's built-in `get' and a quoted string as
178
+ # route)
179
+
180
+ get mmap(%r{/user/mmap/regex/([\w]+)/([\w]+)/?},
181
+ '/user/mmap/regex/:name/:age/', :mmap_regex_user) do |name, age|
182
+ path :mmap_regex_user, :name => name, :age => age
183
+ end
184
+
185
+ # `generate_paths'
186
+
187
+ # Callign `mmap' invokes `generate_paths' and registers a new route.
188
+ # Consequence: `settings.app_paths' cannot be `nil' anymore. This is
189
+ # what the test can test check.
190
+ get mmap('/generate/paths/?', 'generate/paths/', :build_paths) do
191
+ 'Paths have been generated.'
192
+ end
193
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-rroute
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - nounch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |
42
+ Sinatra-rraoute provides `gget'/`ppost'/`ddelete'/... methods which work
43
+ just like Sinatra's built-in `get'/`post'/`delete'/... methods, but which
44
+ map named routes to functions so that they can be referenced in redirects
45
+ etc.
46
+
47
+ The `path' helper will return a route for a certain route name and the
48
+ given values for this route and comes in handy in both, the
49
+ controller/model component of the application, and the view where you can
50
+ use it to render links, assets URLs, AJAX calls...
51
+
52
+ The nestable `nnamespace' method is useful for API versioning and does not
53
+ interfere with other namespace extensions for Sinatra.
54
+ email:
55
+ - nounch@outlook.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".gitignore"
61
+ - Gemfile
62
+ - LICENSE.txt
63
+ - README.md
64
+ - Rakefile
65
+ - lib/sinatra/rroute.rb
66
+ - lib/sinatra/rroute/version.rb
67
+ - rroute.gemspec
68
+ - spec/rroute_spec.rb
69
+ - spec/test_app.rb
70
+ homepage: ''
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.2.2
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Rails-style routes with names, namespaces and `path' helper.
94
+ test_files:
95
+ - spec/rroute_spec.rb
96
+ - spec/test_app.rb
97
+ has_rdoc: