hanami-router 0.0.0 → 0.6.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,39 @@
1
+ module Hanami
2
+ module Routing
3
+ class Resource
4
+ # Helper class to calculate nested path
5
+ #
6
+ # @api private
7
+ # @since 0.4.0
8
+ class Nested
9
+ # @api private
10
+ # @since 0.4.0
11
+ SEPARATOR = '/'.freeze
12
+
13
+ # @api private
14
+ # @since 0.4.0
15
+ def initialize(resource_name, resource)
16
+ @resource_name = resource_name.to_s.split(SEPARATOR)
17
+ @resource = resource
18
+ @path = []
19
+ _calculate(@resource_name.dup, @resource)
20
+ end
21
+
22
+ # @api private
23
+ # @since 0.4.0
24
+ def to_path
25
+ @path.reverse!.pop
26
+ @resource_name.zip(@path).flatten.join
27
+ end
28
+
29
+ private
30
+
31
+ def _calculate(param_wildcard, resource = nil)
32
+ return if resource.nil?
33
+ @path << resource.wildcard_param(param_wildcard.pop)
34
+ _calculate(param_wildcard, resource.parent)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ module Hanami
2
+ module Routing
3
+ class Resource
4
+ # Options for RESTFul resource(s)
5
+ #
6
+ # @api private
7
+ # @since 0.1.0
8
+ #
9
+ # @see Hanami::Router#resource
10
+ # @see Hanami::Router#resources
11
+ class Options
12
+ # @api private
13
+ # @since 0.1.0
14
+ attr_reader :actions
15
+
16
+ # Initialize the options for:
17
+ # * Hanami::Router#resource
18
+ # * Hanami::Router#resources
19
+ #
20
+ # @param actions [Array<Symbol>] the name of the actions
21
+ # @param options [Hash]
22
+ # @option options [Hash] :only white list of the default actions
23
+ # @option options [Hash] :except black list of the default actions
24
+ # @option options [String] :controller namespace for an actions
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ #
29
+ # @see Hanami::Routing::Resource
30
+ # @see Hanami::Routing::Resources
31
+ #
32
+ # @example
33
+ # require 'hanami/router'
34
+ #
35
+ # Hanami::Router.new do
36
+ # resources 'articles', only: [:index]
37
+ # resource 'profile', except: [:new, :create, :destroy]
38
+ # end
39
+ def initialize(actions, options = {})
40
+ only = Array(options.delete(:only) || actions)
41
+ except = Array(options.delete(:except))
42
+ @actions = ( actions & only ) - except
43
+
44
+ @options = options
45
+ end
46
+
47
+ # Return the option for the given key
48
+ #
49
+ # @param key [Symbol] the key that should be searched
50
+ #
51
+ # @return [Object,nil] returns the object associated to the given key
52
+ # or nil, if missing.
53
+ #
54
+ # @api private
55
+ # @since 0.1.1
56
+ def [](key)
57
+ @options[key]
58
+ end
59
+
60
+ # Merge the current options with the given hash, without mutating self.
61
+ #
62
+ # @param hash [Hash] the hash to be merged
63
+ #
64
+ # @return [Hash] the result of the merging operation
65
+ #
66
+ # @api private
67
+ # @since 0.1.1
68
+ def merge(hash)
69
+ @options.merge(hash)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,48 @@
1
+ require 'hanami/routing/resource'
2
+ require 'hanami/routing/resources/action'
3
+
4
+ module Hanami
5
+ module Routing
6
+ # Set of RESTful resources routes
7
+ # Implementation of Hanami::Router#resources
8
+ #
9
+ # @since 0.1.0
10
+ #
11
+ # @api private
12
+ #
13
+ # @see Hanami::Router#resources
14
+ class Resources < Resource
15
+ # Set of default routes
16
+ #
17
+ # @api private
18
+ # @since 0.1.0
19
+ self.actions = [:index, :new, :create, :show, :edit, :update, :destroy]
20
+
21
+ # Action class
22
+ #
23
+ # @api private
24
+ # @since 0.1.0
25
+ self.action = Resources::Action
26
+
27
+ # Member action class
28
+ #
29
+ # @api private
30
+ # @since 0.1.0
31
+ self.member = Resources::MemberAction
32
+
33
+ # Collection action class
34
+ #
35
+ # @api private
36
+ # @since 0.1.0
37
+ self.collection = Resources::CollectionAction
38
+
39
+ # Return wildcard param between separators
40
+ #
41
+ # @api private
42
+ # @since 0.4.0
43
+ def wildcard_param(route_param = nil)
44
+ "/:#{ Hanami::Utils::String.new(route_param).singularize }_id/"
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,150 @@
1
+ require 'hanami/utils/string'
2
+ require 'hanami/utils/path_prefix'
3
+ require 'hanami/routing/resource'
4
+
5
+ module Hanami
6
+ module Routing
7
+ class Resources < Resource
8
+ # Action for RESTful resources
9
+ #
10
+ # @since 0.1.0
11
+ #
12
+ # @api private
13
+ #
14
+ # @see Hanami::Router#resources
15
+ class Action < Resource::Action
16
+ # Ruby namespace where lookup for default subclasses.
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ self.namespace = Resources
21
+
22
+ # Id route variable
23
+ #
24
+ # @since 0.2.0
25
+ # @api private
26
+ class_attribute :identifier
27
+ self.identifier = ':id'.freeze
28
+ end
29
+
30
+ # Pluralize concrete actions
31
+ #
32
+ # @api private
33
+ # @since 0.4.0
34
+ module PluralizedAction
35
+ private
36
+ # The name of the RESTful action.
37
+ #
38
+ # @api private
39
+ # @since 0.4.0
40
+ def as
41
+ Hanami::Utils::String.new(super).pluralize
42
+ end
43
+ end
44
+
45
+ # Collection action
46
+ # It implements #collection within a #resources block.
47
+ #
48
+ # @api private
49
+ # @since 0.1.0
50
+ # @see Hanami::Router#resources
51
+ class CollectionAction < Resource::CollectionAction
52
+ def as(action_name)
53
+ Hanami::Utils::String.new(super(action_name)).pluralize
54
+ end
55
+ end
56
+
57
+ # Member action
58
+ # It implements #member within a #resources block.
59
+ #
60
+ # @api private
61
+ # @since 0.1.0
62
+ # @see Hanami::Router#resources
63
+ class MemberAction < Resource::MemberAction
64
+ private
65
+ def path(action_name)
66
+ rest_path.join(Action.identifier, action_name)
67
+ end
68
+ end
69
+
70
+ # Implementation of common methods for concrete member actions
71
+ #
72
+ # @api private
73
+ # @since 0.1.0
74
+ module DefaultMemberAction
75
+ private
76
+ def path
77
+ rest_path.join(Action.identifier)
78
+ end
79
+ end
80
+
81
+ # Index action
82
+ #
83
+ # @api private
84
+ # @since 0.1.0
85
+ # @see Hanami::Router#resources
86
+ class Index < Action
87
+ include PluralizedAction
88
+ self.verb = :get
89
+ end
90
+
91
+ # New action
92
+ #
93
+ # @api private
94
+ # @since 0.1.0
95
+ # @see Hanami::Router#resources
96
+ class New < Resource::New
97
+ end
98
+
99
+ # Create action
100
+ #
101
+ # @api private
102
+ # @since 0.1.0
103
+ # @see Hanami::Router#resources
104
+ class Create < Resource::Create
105
+ include PluralizedAction
106
+ end
107
+
108
+ # Show action
109
+ #
110
+ # @api private
111
+ # @since 0.1.0
112
+ # @see Hanami::Router#resources
113
+ class Show < Resource::Show
114
+ include DefaultMemberAction
115
+ end
116
+
117
+ # Edit action
118
+ #
119
+ # @api private
120
+ # @since 0.1.0
121
+ # @see Hanami::Router#resources
122
+ class Edit < Resource::Edit
123
+ include DefaultMemberAction
124
+
125
+ private
126
+ def path
127
+ super.join(action_name)
128
+ end
129
+ end
130
+
131
+ # Update action
132
+ #
133
+ # @api private
134
+ # @since 0.1.0
135
+ # @see Hanami::Router#resources
136
+ class Update < Resource::Update
137
+ include DefaultMemberAction
138
+ end
139
+
140
+ # Destroy action
141
+ #
142
+ # @api private
143
+ # @since 0.1.0
144
+ # @see Hanami::Router#resources
145
+ class Destroy < Resource::Destroy
146
+ include DefaultMemberAction
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,62 @@
1
+ require 'http_router/route'
2
+
3
+ module Hanami
4
+ module Routing
5
+ # Entry of the routing system
6
+ #
7
+ # @api private
8
+ #
9
+ # @since 0.1.0
10
+ #
11
+ # @see http://rdoc.info/gems/http_router/HttpRouter/Route
12
+ #
13
+ # @example
14
+ # require 'hanami/router'
15
+ #
16
+ # router = Hanami::Router.new
17
+ # router.get('/', to: endpoint) # => #<Hanami::Routing::Route:0x007f83083ba028 ...>
18
+ class Route < HttpRouter::Route
19
+ # Asks the given resolver to return an endpoint that will be associated
20
+ # with the other options.
21
+ #
22
+ # @param resolver [Hanami::Routing::EndpointResolver, #resolve] this may change
23
+ # according to the :resolve option passed to Hanami::Router#initialize.
24
+ #
25
+ # @param options [Hash] options to customize the route
26
+ # @option options [Symbol] :as the name we want to use for the route
27
+ #
28
+ # @since 0.1.0
29
+ #
30
+ # @api private
31
+ #
32
+ # @see Hanami::Router#initialize
33
+ #
34
+ # @example
35
+ # require 'hanami/router'
36
+ #
37
+ # router = Hanami::Router.new
38
+ # router.get('/', to: endpoint, as: :home_page).name # => :home_page
39
+ #
40
+ # router.path(:home_page) # => '/'
41
+ def generate(resolver, options = {}, &endpoint)
42
+ self.to = resolver.resolve(options, &endpoint)
43
+ self.name = options[:as].to_sym if options[:as]
44
+ self
45
+ end
46
+
47
+ # Introspect the given route to understand if there is a wrapped
48
+ # Hanami::Router
49
+ #
50
+ # @since 0.2.0
51
+ # @api private
52
+ def nested_router
53
+ dest.routes if dest.respond_to?(:routes)
54
+ end
55
+
56
+ private
57
+ def to=(dest = nil, &blk)
58
+ self.to dest, &blk
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,215 @@
1
+ require 'hanami/utils/path_prefix'
2
+
3
+ module Hanami
4
+ module Routing
5
+ # Routes inspector
6
+ #
7
+ # @since 0.2.0
8
+ class RoutesInspector
9
+ # Default route formatter
10
+ #
11
+ # @since 0.2.0
12
+ # @api private
13
+ FORMATTER = "%<name>20s %<methods>-10s %<path>-30s %<endpoint>-30s\n".freeze
14
+
15
+ # Default HTTP methods separator
16
+ #
17
+ # @since 0.2.0
18
+ # @api private
19
+ HTTP_METHODS_SEPARATOR = ', '.freeze
20
+
21
+ # Default inspector header hash values
22
+ #
23
+ # @since 0.5.0
24
+ # @api private
25
+ INSPECTOR_HEADER_HASH = Hash[
26
+ name: 'Name',
27
+ methods: 'Method',
28
+ path: 'Path',
29
+ endpoint: 'Action'
30
+ ].freeze
31
+
32
+ # Default inspector header name values
33
+ #
34
+ # @since 0.5.0
35
+ # @api private
36
+ INSPECTOR_HEADER_NAME = 'Name'.freeze
37
+
38
+ # Empty line string
39
+ #
40
+ # @since 0.5.0
41
+ # @api private
42
+ EMPTY_LINE = "\n".freeze
43
+
44
+ # Instantiate a new inspector
45
+ #
46
+ # @return [Hanami::Routing::RoutesInspector] the new instance
47
+ #
48
+ # @since 0.2.0
49
+ # @api private
50
+ def initialize(routes)
51
+ @routes = routes
52
+ end
53
+
54
+ # Return a formatted string that describes all the routes
55
+ #
56
+ # @param formatter [String] the optional formatter for a route
57
+ # @param base_path [String] the base path of a nested route
58
+ #
59
+ # @return [String] routes pretty print
60
+ #
61
+ # @since 0.2.0
62
+ #
63
+ # @see Hanami::Routing::RoutesInspector::FORMATTER
64
+ #
65
+ # @example Default formatter
66
+ # require 'hanami/router'
67
+ #
68
+ # router = Hanami::Router.new do
69
+ # get '/', to: 'home#index'
70
+ # get '/login', to: 'sessions#new', as: :login
71
+ # post '/login', to: 'sessions#create'
72
+ # delete '/logout', to: 'sessions#destroy', as: :logout
73
+ # end
74
+ #
75
+ # puts router.inspector.to_s
76
+ # # => Name Method Path Action
77
+ #
78
+ # GET, HEAD / Home::Index
79
+ # login GET, HEAD /login Sessions::New
80
+ # POST /login Sessions::Create
81
+ # logout GET, HEAD /logout Sessions::Destroy
82
+ #
83
+ # @example Custom formatter
84
+ # require 'hanami/router'
85
+ #
86
+ # router = Hanami::Router.new do
87
+ # get '/', to: 'home#index'
88
+ # get '/login', to: 'sessions#new', as: :login
89
+ # post '/login', to: 'sessions#create'
90
+ # delete '/logout', to: 'sessions#destroy', as: :logout
91
+ # end
92
+ #
93
+ # formatter = "| %{methods} | %{name} | %{path} | %{endpoint} |\n"
94
+ #
95
+ # puts router.inspector.to_s(formatter)
96
+ # # => | Method | Name | Path | Action |
97
+ #
98
+ # | GET, HEAD | | / | Home::Index |
99
+ # | GET, HEAD | login | /login | Sessions::New |
100
+ # | POST | | /login | Sessions::Create |
101
+ # | GET, HEAD | logout | /logout | Sessions::Destroy |
102
+ #
103
+ # @example Nested routes
104
+ # require 'hanami/router'
105
+ #
106
+ # class AdminHanamiApp
107
+ # def call(env)
108
+ # end
109
+ # def routes
110
+ # Hanami::Router.new {
111
+ # get '/home', to: 'home#index'
112
+ # }
113
+ # end
114
+ # end
115
+ #
116
+ # router = Hanami::Router.new {
117
+ # get '/fakeroute', to: 'fake#index'
118
+ # mount AdminHanamiApp, at: '/admin'
119
+ # mount Hanami::Router.new {
120
+ # get '/posts', to: 'posts#index'
121
+ # mount Hanami::Router.new {
122
+ # get '/comments', to: 'comments#index'
123
+ # }, at: '/second_mount'
124
+ # }, at: '/api'
125
+ # }
126
+ #
127
+ # formatter = "| %{methods} | %{name} | %{path} | %{endpoint} |\n"
128
+ #
129
+ # puts router.inspector.to_s(formatter)
130
+ # # => | Method | Name | Path | Action |
131
+ #
132
+ # | GET, HEAD | | /fakeroute | Fake::Index |
133
+ # | GET, HEAD | | /admin/home | Home::Index |
134
+ # | GET, HEAD | | /api/posts | Posts::Index |
135
+ # | GET, HEAD | | /api/second_mount/comments | Comments::Index |
136
+ def to_s(formatter = FORMATTER, base_path = nil)
137
+ base_path = Utils::PathPrefix.new(base_path)
138
+
139
+ inspect_routes(formatter, base_path)
140
+ .insert(0, formatter % INSPECTOR_HEADER_HASH + EMPTY_LINE)
141
+ end
142
+
143
+ # Returns a string representation of routes
144
+ #
145
+ # @param formatter [String] the template for the output
146
+ # @param base_path [Hanami::Utils::PathPrefix] the base path
147
+ #
148
+ # @return [String] serialized routes from router
149
+ #
150
+ # @since 0.5.1
151
+ # @api private
152
+ #
153
+ # @see Hanami::Routing::RoutesInspector#FORMATTER
154
+ # @see Hanami::Routing::RoutesInspector#to_s
155
+ def inspect_routes(formatter, base_path)
156
+ result = ''
157
+
158
+ # TODO refactoring: replace conditional with polymorphism
159
+ # We're exposing too much knowledge from Routing::Route:
160
+ # #path_for_generation and #base_path
161
+ @routes.each do |route|
162
+ result << if router = route.nested_router
163
+ inspect_router(formatter, router, route, base_path)
164
+ else
165
+ inspect_route(formatter, route, base_path)
166
+ end
167
+ end
168
+
169
+ result
170
+ end
171
+
172
+ private
173
+
174
+ # Returns a string representation of the given route
175
+ #
176
+ # @param formatter [String] the template for the output
177
+ # @param route [Hanami::Routing::Route] a route
178
+ # @param base_path [Hanami::Utils::PathPrefix] the base path
179
+ #
180
+ # @return [String] serialized route
181
+ #
182
+ # @since 0.2.0
183
+ # @api private
184
+ #
185
+ # @see Hanami::Routing::RoutesInspector#FORMATTER
186
+ # @see Hanami::Routing::RoutesInspector#to_s
187
+ def inspect_route(formatter, route, base_path)
188
+ formatter % Hash[
189
+ name: route.name,
190
+ methods: route.request_methods.to_a.join(HTTP_METHODS_SEPARATOR),
191
+ path: base_path.join(route.path_for_generation),
192
+ endpoint: route.dest.inspect
193
+ ]
194
+ end
195
+
196
+ # Returns a string representation of the given router
197
+ #
198
+ # @param formatter [String] the template for the output
199
+ # @param router [Hanami::Router] a router
200
+ # @param route [Hanami::Routing::Route] a route
201
+ # @param base_path [Hanami::Utils::PathPrefix] the base path
202
+ #
203
+ # @return [String] serialized routes from router
204
+ #
205
+ # @since 0.2.0
206
+ # @api private
207
+ #
208
+ # @see Hanami::Routing::RoutesInspector#FORMATTER
209
+ # @see Hanami::Routing::RoutesInspector#to_s
210
+ def inspect_router(formatter, router, route, base_path)
211
+ router.inspector.inspect_routes(formatter, base_path.join(route.path_for_generation))
212
+ end
213
+ end
214
+ end
215
+ end