signpost 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a59506bcf00a7ee073cb30b7a122231f85e513ca
4
+ data.tar.gz: 623c980473e5f4a33faa69f7b646faa58a068c8e
5
+ SHA512:
6
+ metadata.gz: 5f4e3d1381b106061d28ec8e67d94d6d3625a758051cdcb9c1e11a7cd3fed1c20b5276f26c14c5a93116e29b389f15b522381bc0e5ee5e90b190ef968c8eebc0
7
+ data.tar.gz: 5d2cd384d8462b7ab734f14e3789cc6a92c9fbc78cd624e3bbf1ad6ee967359476bda4ba6e378f276d2ba90b18789db3a1f7c1cc130db228c744fc88af213f73
@@ -0,0 +1,29 @@
1
+ require 'set'
2
+ require 'mustermann'
3
+ require 'inflecto'
4
+
5
+ class Signpost
6
+ SUPPORTED_METHODS = %w(GET POST PUT PATCH OPTIONS DELETE).map(&:freeze).to_set.freeze
7
+ end
8
+
9
+ require 'signpost/version'
10
+ require 'signpost/route/simple'
11
+ require 'signpost/route/nested'
12
+ require 'signpost/router'
13
+
14
+ require 'signpost/middleware'
15
+
16
+ require 'signpost/endpoint/resolver'
17
+ require 'signpost/endpoint/builder'
18
+ require 'signpost/endpoint/dynamic'
19
+ require 'signpost/endpoint/redirect'
20
+ require 'signpost/endpoint/action'
21
+
22
+ require 'signpost/sign/flat'
23
+ require 'signpost/sign/flat/path'
24
+ require 'signpost/sign/flat/redirect'
25
+ require 'signpost/sign/nested'
26
+ require 'signpost/sign/namespace'
27
+ require 'signpost/builder/nested'
28
+ require 'signpost/builder/namespace'
29
+ require 'signpost/builder'
@@ -0,0 +1,416 @@
1
+ class Signpost
2
+ class Builder
3
+ DEFAULT_OPTIONS = {
4
+ params_key: 'router.params',
5
+ default_redirect_status: 303,
6
+ default_redirect_additional_values: :ignore,
7
+ style: :sinatra,
8
+ middlewares: [],
9
+ rack_params: false
10
+ }.freeze
11
+ SUBPATH_REG = /^\//.freeze
12
+
13
+ ##
14
+ # Define root route
15
+ #
16
+ # Root route will always be in top of routes
17
+ # Also this is named route with name `root`
18
+ #
19
+ # Example:
20
+ #
21
+ # root.to(HomeController)
22
+ #
23
+ def root(&block)
24
+ builder = Sign::Flat::Path::GET.new(absolute('/'), @options, &block)
25
+ @builders.unshift(builder)
26
+ builder.as(root_name)
27
+ end
28
+
29
+ ##
30
+ # Define route which accepts GET request for the given pattern
31
+ #
32
+ # Params:
33
+ # - path {String|RegExp} pattern of the request path which should be matched
34
+ #
35
+ # Yields: the Rack compatible block
36
+ #
37
+ # Example:
38
+ #
39
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
40
+ # get('/users').to(controller: 'Users', action: 'index')
41
+ # get('/users').to(controller: UsersController, action: 'index')
42
+ # get('/users').to('users#index')
43
+ #
44
+ # # Single action controller (responds to .call)
45
+ # get('/').to(HomeController)
46
+ #
47
+ # # Sinatra-style block action
48
+ # get('/echo').to do |env|
49
+ # [200, {}, [env['SERVER_NAME']]]
50
+ # end
51
+ # get('/echo') do |env|
52
+ # [200, {}, [env['SERVER_NAME']]]
53
+ # end
54
+ #
55
+ # # Path params
56
+ # get('/users/:id').to('users#show')
57
+ #
58
+ # # Params constraints
59
+ # get('/users/:id').to('users#show').capture(:digit)
60
+ # get('/users/:id.:ext').to('pages#show').capture({ id: /\d+/, ext: ['json', 'html'] })
61
+ #
62
+ # # Exclude pattern
63
+ # get('/pages/*slug/edit').to('pages#edit').except('/pages/system/*/edit')
64
+ #
65
+ # # Default params
66
+ # get('/:controller/:action/:id').params(id: 1)
67
+ #
68
+ def get(path, &block)
69
+ builder = Sign::Flat::Path::GET.new(absolute(path), @options, &block)
70
+ @builders << builder
71
+ builder
72
+ end
73
+
74
+ ##
75
+ # Define route which accepts POST request for the given pattern
76
+ #
77
+ # Params:
78
+ # - path {String|RegExp} pattern of the request path which should be matched
79
+ #
80
+ # Yields: the Rack compatible block
81
+ #
82
+ # Example:
83
+ #
84
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
85
+ # post('/users').to(controller: 'Users', action: 'create')
86
+ # post('/users').to(controller: UsersController, action: 'create')
87
+ # post('/users').to('users#create')
88
+ #
89
+ # # Single action controller (responds to .call)
90
+ # post('/search').to(SearchController)
91
+ #
92
+ # # Sinatra-style block action
93
+ # post('/echo').to do |env|
94
+ # [201, {}, [env['SERVER_NAME']]]
95
+ # end
96
+ # post('/echo') do |env|
97
+ # [201, {}, [env['SERVER_NAME']]]
98
+ # end
99
+ #
100
+ # # Path params
101
+ # post('/users/:id').to('users#update')
102
+ #
103
+ # # Params constraints
104
+ # post('/users/:id').to('users#update').capture(:digit)
105
+ # post('/users/:id.:ext').to('pages#show').capture({ id: /\d+/, ext: ['json', 'html'] })
106
+ #
107
+ # # Exclude pattern
108
+ # post('/pages/*slug/update').to('pages#update').except('/pages/system/*/update')
109
+ #
110
+ # # Default params
111
+ # post('/:controller/:action/:id').params(id: 1)
112
+ #
113
+ def post(path, &block)
114
+ builder = Sign::Flat::Path::POST.new(absolute(path), @options, &block)
115
+ @builders << builder
116
+ builder
117
+ end
118
+
119
+ ##
120
+ # Define route which accepts PUT request for the given pattern
121
+ #
122
+ # Params:
123
+ # - path {String|RegExp} pattern of the request path which should be matched
124
+ #
125
+ # Yields: the Rack compatible block
126
+ #
127
+ # Example:
128
+ #
129
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
130
+ # put('/users').to(controller: 'Users', action: 'create')
131
+ # put('/users').to(controller: UsersController, action: 'create')
132
+ # put('/users').to('users#create')
133
+ #
134
+ # # Single action controller (responds to .call)
135
+ # put('/search').to(SearchController)
136
+ #
137
+ # # Sinatra-style block action
138
+ # put('/echo').to do |env|
139
+ # [201, {}, [env['SERVER_NAME']]]
140
+ # end
141
+ # put('/echo') do |env|
142
+ # [201, {}, [env['SERVER_NAME']]]
143
+ # end
144
+ #
145
+ # # Path params
146
+ # put('/users/:id').to('users#update')
147
+ #
148
+ # # Params constraints
149
+ # put('/users/:id').to('users#update').capture(:digit)
150
+ # put('/users/:id.:ext').to('pages#show').capture({ id: /\d+/, ext: ['json', 'html'] })
151
+ #
152
+ # # Exclude pattern
153
+ # put('/pages/*slug/update').to('pages#update').except('/pages/system/*/update')
154
+ #
155
+ # # Default params
156
+ # put('/:controller/:action/:id').params(id: 1)
157
+ #
158
+ def put(path, &block)
159
+ builder = Sign::Flat::Path::PUT.new(absolute(path), @options, &block)
160
+ @builders << builder
161
+ builder
162
+ end
163
+
164
+ ##
165
+ # Define route which accepts PATCH request for the given pattern
166
+ #
167
+ # Params:
168
+ # - path {String|RegExp} pattern of the request path which should be matched
169
+ #
170
+ # Yields: the Rack compatible block
171
+ #
172
+ # Example:
173
+ #
174
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
175
+ # patch('/users/:id').to(controller: 'Users', action: 'update')
176
+ # patch('/users/:id').to(controller: UsersController, action: 'update')
177
+ # patch('/users/:id').to('users#update')
178
+ #
179
+ # # Single action controller (responds to .call)
180
+ # patch('/users/:id').to(UsersUpdateController)
181
+ #
182
+ # # Sinatra-style block action
183
+ # patch('/echo').to do |env|
184
+ # [201, {}, [env['SERVER_NAME']]]
185
+ # end
186
+ # patch('/echo') do |env|
187
+ # [201, {}, [env['SERVER_NAME']]]
188
+ # end
189
+ #
190
+ # # Params constraints
191
+ # patch('/users/:id').to('users#update').capture(:digit)
192
+ # patch('/users/:id.:ext').to('pages#show').capture({ id: /\d+/, ext: ['json', 'html'] })
193
+ #
194
+ # # Exclude pattern
195
+ # patch('/pages/*slug/update').to('pages#update').except('/pages/system/*/update')
196
+ #
197
+ # # Default params
198
+ # patch('/:controller/:action/:id').params(id: 1)
199
+ #
200
+ def patch(path, &block)
201
+ builder = Sign::Flat::Path::PATCH.new(absolute(path), @options, &block)
202
+ @builders << builder
203
+ builder
204
+ end
205
+
206
+ ##
207
+ # Define route which accepts OPTIONS request for the given pattern
208
+ #
209
+ # Params:
210
+ # - path {String|RegExp} pattern of the request path which should be matched
211
+ #
212
+ # Yields: the Rack compatible block
213
+ #
214
+ # Example:
215
+ #
216
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
217
+ # options('/users').to(controller: 'Users', action: 'usage')
218
+ # options('/users').to(controller: UsersController, action: 'usage')
219
+ # options('/users').to('users#usage')
220
+ #
221
+ # # Single action controller (responds to .call)
222
+ # options('/').to(HomeController)
223
+ #
224
+ # # Sinatra-style block action
225
+ # options('/echo').to do |env|
226
+ # [200, {}, [env['SERVER_NAME']]]
227
+ # end
228
+ # options('/echo') do |env|
229
+ # [200, {}, [env['SERVER_NAME']]]
230
+ # end
231
+ #
232
+ # # Path params
233
+ # options('/users/:id').to('users#show')
234
+ #
235
+ # # Params constraints
236
+ # options('/users/:id').to('users#usage').capture(:digit)
237
+ # options('/users/:id.:ext').to('pages#usage').capture({ id: /\d+/, ext: ['json', 'html'] })
238
+ #
239
+ # # Exclude pattern
240
+ # options('/pages/*slug/usage').to('pages#usage').except('/pages/private/*/usage')
241
+ #
242
+ # # Default params
243
+ # options('/:controller/:action/:id').params(id: 1)
244
+ #
245
+ def options(path, &block)
246
+ builder = Sign::Flat::Path::OPTIONS.new(absolute(path), @options, &block)
247
+ @builders << builder
248
+ builder
249
+ end
250
+
251
+ ##
252
+ # Define route which accepts PATCH request for the given pattern
253
+ #
254
+ # Params:
255
+ # - path {String|RegExp} pattern of the request path which should be matched
256
+ #
257
+ # Yields: the Rack compatible block
258
+ #
259
+ # Example:
260
+ #
261
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
262
+ # delete('/users/:id').to(controller: 'Users', action: 'destroy')
263
+ # delete('/users/:id').to(controller: UsersController, action: 'destroy')
264
+ # delete('/users/:id').to('users#destroy')
265
+ #
266
+ # # Single action controller (responds to .call)
267
+ # delete('/users/:id').to(UsersDestroyController)
268
+ #
269
+ # # Sinatra-style block action
270
+ # delete('/echo').to do |env|
271
+ # [201, {}, [env['SERVER_NAME']]]
272
+ # end
273
+ # delete('/echo') do |env|
274
+ # [201, {}, [env['SERVER_NAME']]]
275
+ # end
276
+ #
277
+ # # Params constraints
278
+ # delete('/users/:id').to('users#destroy').capture(:digit)
279
+ # delete('/users/:id.:ext').to('pages#destroy').capture({ id: /\d+/, ext: ['json', 'html'] })
280
+ #
281
+ # # Exclude pattern
282
+ # delete('/pages/*slug/destroy').to('pages#destroy').except('/pages/system/*/destroy')
283
+ #
284
+ # # Default params
285
+ # delete('/:controller/:action/:id').params(id: 1)
286
+ #
287
+ def delete(path, &block)
288
+ builder = Sign::Flat::Path::DELETE.new(absolute(path), @options, &block)
289
+ @builders << builder
290
+ builder
291
+ end
292
+
293
+ ##
294
+ # Define route which accepts any type (or specified list) of request for the given pattern
295
+ #
296
+ # Params:
297
+ # - path {String|RegExp} pattern of the request path which should be matched
298
+ #
299
+ # Yields: the Rack compatible block
300
+ #
301
+ # Example:
302
+ #
303
+ # # Most usual case: with controller and action. Will resolve Controller#action or Controller::Action
304
+ # match('/users').to(controller: 'Users', action: 'index')
305
+ # match('/users').to(controller: UsersController, action: 'index')
306
+ # match('/users').to('users#index')
307
+ #
308
+ # # Specify types of requests
309
+ # match('/users').to('users#create').via(:post, :put)
310
+ #
311
+ # # Single action controller (responds to .call)
312
+ # match('/').to(HomeController)
313
+ #
314
+ # # Sinatra-style block action
315
+ # match('/echo').to do |env|
316
+ # [200, {}, [env['SERVER_NAME']]]
317
+ # end
318
+ # match('/echo') do |env|
319
+ # [200, {}, [env['SERVER_NAME']]]
320
+ # end
321
+ #
322
+ # # Path params
323
+ # match('/users/:id').to('users#show')
324
+ #
325
+ # # Params constraints
326
+ # match('/users/:id').to('users#show').capture(:digit)
327
+ # match('/users/:id.:ext').to('pages#show').capture({ id: /\d+/, ext: ['json', 'html'] })
328
+ #
329
+ # # Exclude pattern
330
+ # match('/pages/*slug/edit').to('pages#edit').except('/pages/system/*/edit')
331
+ #
332
+ # # Default params
333
+ # match('/:controller/:action/:id').params(id: 1)
334
+ #
335
+ def match(path, &block)
336
+ builder = Sign::Flat::Path::Any.new(absolute(path), @options, &block)
337
+ @builders << builder
338
+ builder
339
+ end
340
+
341
+ ##
342
+ # Define nested routes
343
+ #
344
+ # Params:
345
+ # - path {String} subpath
346
+ #
347
+ # Yields: routes dsl
348
+ #
349
+ # Example:
350
+ #
351
+ # within('/admin') do
352
+ # get('/pages').to('Admin::Pages#index') # /admin/pages
353
+ # put('/users/:id').to('admin/users#create') # /admin/users/2
354
+ # end
355
+ #
356
+ # # Nested routes can has their own routes
357
+ # within('/admin') do
358
+ # get('/').to('admin#index')
359
+ #
360
+ # within('/pages') do
361
+ # get('/').to('admin/pages#index') # /admin/pages
362
+ # put('/:id').to('admin/pages#create') # /admin/pages/2
363
+ # end
364
+ # end
365
+ #
366
+ # # You can also build a middleware stack for subroutes
367
+ # within('/admin') do
368
+ # use AuthMiddleware
369
+ #
370
+ # get('/').to('admin#index')
371
+ # end
372
+ #
373
+ def within(path, &block)
374
+ @builders << Sign::Nested.new(absolute(path), @options, &block)
375
+ end
376
+
377
+ def namespace(name, &block)
378
+ options = @options.merge(namespace: [@options[:namespace], name].compact)
379
+ @builders << Sign::Namespace.new(absolute(name.to_s), options, &block)
380
+ end
381
+
382
+ def redirect(path, &block)
383
+ builder = Sign::Flat::Redirect.new(absolute(path), @options, &block)
384
+ @builders << builder
385
+ builder
386
+ end
387
+
388
+ ##
389
+ # Build router
390
+ #
391
+ # Returns: {Signpost::Router}
392
+ #
393
+ def build
394
+ Router.new(@builders, @options, true)
395
+ end
396
+
397
+ private
398
+
399
+ def initialize(options={}, &block)
400
+ @options = DEFAULT_OPTIONS.merge(options)
401
+ @subroute = options[:subroute] || '/'
402
+ @builders = []
403
+
404
+ instance_eval(&block) if block_given?
405
+ end
406
+
407
+ def absolute(path)
408
+ File.join(@subroute, path.gsub(SUBPATH_REG, ''))
409
+ end
410
+
411
+ def root_name
412
+ 'root'
413
+ end
414
+
415
+ end
416
+ end