piko-lite-box 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 +7 -0
- data/hanami-3.0.1/CHANGELOG.md +1701 -0
- data/hanami-3.0.1/LICENSE +20 -0
- data/hanami-3.0.1/README.md +89 -0
- data/hanami-3.0.1/hanami.gemspec +49 -0
- data/hanami-3.0.1/lib/hanami/app.rb +197 -0
- data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
- data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
- data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
- data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
- data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
- data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
- data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
- data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
- data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
- data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
- data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
- data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
- data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
- data/hanami-3.0.1/lib/hanami/config.rb +564 -0
- data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
- data/hanami-3.0.1/lib/hanami/env.rb +52 -0
- data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
- data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
- data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
- data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
- data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
- data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
- data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
- data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
- data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
- data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
- data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
- data/hanami-3.0.1/lib/hanami/port.rb +45 -0
- data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
- data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
- data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
- data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
- data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
- data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
- data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
- data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
- data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
- data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
- data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
- data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
- data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
- data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
- data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
- data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
- data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
- data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
- data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
- data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
- data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
- data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
- data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
- data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
- data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
- data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
- data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
- data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
- data/hanami-3.0.1/lib/hanami/version.rb +45 -0
- data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
- data/hanami-3.0.1/lib/hanami.rb +276 -0
- data/piko-lite-box.gemspec +11 -0
- metadata +137 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/router"
|
|
4
|
+
require_relative "routing/resolver"
|
|
5
|
+
require_relative "routing/middleware/stack"
|
|
6
|
+
|
|
7
|
+
module Hanami
|
|
8
|
+
class Slice
|
|
9
|
+
# `Hanami::Router` subclass with enhancements for use within Hanami apps.
|
|
10
|
+
#
|
|
11
|
+
# This is loaded from Hanami apps and slices and made available as their
|
|
12
|
+
# {Hanami::Slice::ClassMethods#router router}.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
class Router < ::Hanami::Router
|
|
16
|
+
# @api private
|
|
17
|
+
attr_reader :inflector
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
attr_reader :middleware_stack
|
|
21
|
+
|
|
22
|
+
# @api private
|
|
23
|
+
attr_reader :path_prefix
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
def initialize(
|
|
27
|
+
routes:,
|
|
28
|
+
inflector:,
|
|
29
|
+
middleware_stack: Routing::Middleware::Stack.new,
|
|
30
|
+
prefix: ::Hanami::Router::DEFAULT_PREFIX,
|
|
31
|
+
**kwargs,
|
|
32
|
+
&blk
|
|
33
|
+
)
|
|
34
|
+
@path_prefix = Hanami::Router::Prefix.new(prefix)
|
|
35
|
+
@inflector = inflector
|
|
36
|
+
@middleware_stack = middleware_stack
|
|
37
|
+
@resource_scope = []
|
|
38
|
+
instance_eval(&blk)
|
|
39
|
+
super(**kwargs, &routes)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @api private
|
|
43
|
+
def freeze
|
|
44
|
+
return self if frozen?
|
|
45
|
+
|
|
46
|
+
remove_instance_variable(:@middleware_stack)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api private
|
|
51
|
+
def to_rack_app
|
|
52
|
+
middleware_stack.to_rack_app(self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api private
|
|
56
|
+
def use(*args, **kwargs, &blk)
|
|
57
|
+
middleware_stack.use(*args, **kwargs.merge(path_prefix: path_prefix.to_s), &blk)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Yields a block for routes to resolve their action components from the given slice.
|
|
61
|
+
#
|
|
62
|
+
# An optional URL prefix may be supplied with `at:`.
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# # config/routes.rb
|
|
66
|
+
#
|
|
67
|
+
# module MyApp
|
|
68
|
+
# class Routes < Hanami::Routes
|
|
69
|
+
# slice :admin, at: "/admin" do
|
|
70
|
+
# # Will route to the "actions.posts.index" component in Admin::Slice
|
|
71
|
+
# get "posts", to: "posts.index"
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# @param slice_name [Symbol] the slice's name
|
|
77
|
+
# @param at [String, nil] optional URL prefix for the routes
|
|
78
|
+
#
|
|
79
|
+
# @api public
|
|
80
|
+
# @since 2.0.0
|
|
81
|
+
def slice(slice_name, at:, as: nil, &blk)
|
|
82
|
+
blk ||= @resolver.find_slice(slice_name).routes
|
|
83
|
+
|
|
84
|
+
prev_resolver = @resolver
|
|
85
|
+
@resolver = @resolver.to_slice(slice_name)
|
|
86
|
+
|
|
87
|
+
scope(at, as:, &blk)
|
|
88
|
+
ensure
|
|
89
|
+
@resolver = prev_resolver
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Generates RESTful routes for a plural resource.
|
|
93
|
+
#
|
|
94
|
+
# @param name [Symbol] the resource name (plural)
|
|
95
|
+
# @param options [Hash] options for customizing the routes
|
|
96
|
+
# @option options [Array<Symbol>] :only Limit to specific actions
|
|
97
|
+
# @option options [Array<Symbol>] :except Exclude specific actions
|
|
98
|
+
# @option options [String] :to the action key namespace, e.g. "namespace.action"
|
|
99
|
+
# @option options [String] :path the URL path
|
|
100
|
+
# @option options [String, Symbol] :as the route name prefix
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# resources :users
|
|
104
|
+
# # Generates:
|
|
105
|
+
# # GET /users users.index
|
|
106
|
+
# # GET /users/new users.new
|
|
107
|
+
# # POST /users users.create
|
|
108
|
+
# # GET /users/:id users.show
|
|
109
|
+
# # GET /users/:id/edit users.edit
|
|
110
|
+
# # PATCH /users/:id users.update
|
|
111
|
+
# # DELETE /users/:id users.destroy
|
|
112
|
+
#
|
|
113
|
+
# @api public
|
|
114
|
+
# @since 2.3.0
|
|
115
|
+
def resources(name, **options, &block)
|
|
116
|
+
build_resource(name, :plural, options, &block)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Generates RESTful routes for a singular resource.
|
|
120
|
+
#
|
|
121
|
+
# @param name [Symbol] the resource name (singular)
|
|
122
|
+
# @param options [Hash] options for customizing the routes
|
|
123
|
+
# @option options [Array<Symbol>] :only limit to specific actions
|
|
124
|
+
# @option options [Array<Symbol>] :except exclude specific actions
|
|
125
|
+
# @option options [String] :to the action key namespace, e.g. "namespace.action"
|
|
126
|
+
# @option options [String] :path the URL path
|
|
127
|
+
# @option options [String, Symbol] :as the route name prefix
|
|
128
|
+
#
|
|
129
|
+
# @example
|
|
130
|
+
# resource :profile
|
|
131
|
+
# # Generates (singular, no index):
|
|
132
|
+
# # GET /profile/new profile.new
|
|
133
|
+
# # POST /profile profile.create
|
|
134
|
+
# # GET /profile profile.show
|
|
135
|
+
# # GET /profile/edit profile.edit
|
|
136
|
+
# # PATCH /profile profile.update
|
|
137
|
+
# # DELETE /profile profile.destroy
|
|
138
|
+
#
|
|
139
|
+
# @api public
|
|
140
|
+
# @since 2.3.0
|
|
141
|
+
def resource(name, **options, &block)
|
|
142
|
+
build_resource(name, :singular, options, &block)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def build_resource(name, type, options, &block)
|
|
148
|
+
resource_builder = ResourceBuilder.new(
|
|
149
|
+
name: name,
|
|
150
|
+
type: type,
|
|
151
|
+
resource_scope: @resource_scope,
|
|
152
|
+
options: options,
|
|
153
|
+
inflector: inflector
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
resource_builder.add_routes(self)
|
|
157
|
+
resource_builder.scope(self, &block) if block
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Builds RESTful routes for a resource
|
|
161
|
+
#
|
|
162
|
+
# @api private
|
|
163
|
+
class ResourceBuilder
|
|
164
|
+
ROUTE_OPTIONS = {
|
|
165
|
+
index: {method: :get},
|
|
166
|
+
new: {method: :get, path_suffix: "/new", name_prefix: "new"},
|
|
167
|
+
create: {method: :post},
|
|
168
|
+
show: {method: :get, path_suffix: "/:id"},
|
|
169
|
+
edit: {method: :get, path_suffix: "/:id/edit", name_prefix: "edit"},
|
|
170
|
+
update: {method: :patch, path_suffix: "/:id"},
|
|
171
|
+
destroy: {method: :delete, path_suffix: "/:id"}
|
|
172
|
+
}.freeze
|
|
173
|
+
|
|
174
|
+
PLURAL_ACTIONS = %i[index new create show edit update destroy].freeze
|
|
175
|
+
SINGULAR_ACTIONS = (PLURAL_ACTIONS - %i[index]).freeze
|
|
176
|
+
|
|
177
|
+
def initialize(name:, type:, resource_scope:, options:, inflector:)
|
|
178
|
+
@name = name
|
|
179
|
+
@type = type
|
|
180
|
+
@resource_scope = resource_scope
|
|
181
|
+
@options = options
|
|
182
|
+
@inflector = inflector
|
|
183
|
+
|
|
184
|
+
@path = options[:path] || name.to_s
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def scope(router, &block)
|
|
188
|
+
@resource_scope.push(@name)
|
|
189
|
+
router.scope(scope_path, as: scope_name, &block)
|
|
190
|
+
ensure
|
|
191
|
+
@resource_scope.pop
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def add_routes(router)
|
|
195
|
+
actions.each do |action|
|
|
196
|
+
add_route(router, action, ROUTE_OPTIONS.fetch(action))
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def plural?
|
|
203
|
+
@type == :plural
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def singular?
|
|
207
|
+
!plural?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def scope_path
|
|
211
|
+
if plural?
|
|
212
|
+
"#{@path}/:#{@inflector.singularize(@path.to_s)}_id"
|
|
213
|
+
else
|
|
214
|
+
@path
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def scope_name
|
|
219
|
+
@inflector.singularize(@name)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def actions
|
|
223
|
+
default_actions = plural? ? PLURAL_ACTIONS : SINGULAR_ACTIONS
|
|
224
|
+
if @options[:only]
|
|
225
|
+
Array(@options[:only]) & default_actions
|
|
226
|
+
elsif @options[:except]
|
|
227
|
+
default_actions - Array(@options[:except])
|
|
228
|
+
else
|
|
229
|
+
default_actions
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def add_route(router, action, route_options)
|
|
234
|
+
path = "/#{@path}#{route_suffix(route_options[:path_suffix])}"
|
|
235
|
+
to = "#{key_path_base}#{CONTAINER_KEY_DELIMITER}#{action}"
|
|
236
|
+
as = route_name(action, route_options[:name_prefix])
|
|
237
|
+
|
|
238
|
+
router.public_send(route_options[:method], path, to:, as:)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def route_suffix(suffix)
|
|
242
|
+
return suffix.sub(LEADING_ID_REGEX, "") if suffix && singular?
|
|
243
|
+
|
|
244
|
+
suffix
|
|
245
|
+
end
|
|
246
|
+
LEADING_ID_REGEX = %r{\A/:id}
|
|
247
|
+
|
|
248
|
+
def key_path_base
|
|
249
|
+
@key_path_base ||= @options[:to] || begin
|
|
250
|
+
if @resource_scope.any?
|
|
251
|
+
prefix = @resource_scope.join(CONTAINER_KEY_DELIMITER)
|
|
252
|
+
"#{prefix}#{CONTAINER_KEY_DELIMITER}#{@name}"
|
|
253
|
+
else
|
|
254
|
+
@name.to_s
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def route_name(action, prefix)
|
|
260
|
+
name = route_name_base
|
|
261
|
+
name = @inflector.pluralize(name) if plural? && PLURALIZED_NAME_ACTIONS.include?(action)
|
|
262
|
+
|
|
263
|
+
[prefix, name]
|
|
264
|
+
end
|
|
265
|
+
PLURALIZED_NAME_ACTIONS = %i[index create].freeze
|
|
266
|
+
|
|
267
|
+
def route_name_base
|
|
268
|
+
@route_name_base ||=
|
|
269
|
+
if @options[:as]
|
|
270
|
+
@options[:as].to_s
|
|
271
|
+
elsif plural?
|
|
272
|
+
@inflector.singularize(@name.to_s)
|
|
273
|
+
else
|
|
274
|
+
@name.to_s
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Slice
|
|
5
|
+
# Hanami app routes helpers
|
|
6
|
+
#
|
|
7
|
+
# An instance of this class will be registered with slice (at the "routes" key). You
|
|
8
|
+
# can use it to access the route helpers for your app.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# MyApp::App["routes"].path(:root) # => "/"
|
|
12
|
+
#
|
|
13
|
+
# @see Hanami::Router::UrlHelpers
|
|
14
|
+
# @since 2.0.0
|
|
15
|
+
class RoutesHelper
|
|
16
|
+
# @since 2.0.0
|
|
17
|
+
# @api private
|
|
18
|
+
def initialize(router)
|
|
19
|
+
@router = router
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see Hanami::Router::UrlHelpers#path
|
|
23
|
+
def path(...)
|
|
24
|
+
router.path(...)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @see Hanami::Router::UrlHelpers#url
|
|
28
|
+
def url(...)
|
|
29
|
+
router.url(...)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :router
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/router"
|
|
4
|
+
require "hanami/middleware"
|
|
5
|
+
require "hanami/middleware/app"
|
|
6
|
+
require "hanami/errors"
|
|
7
|
+
|
|
8
|
+
module Hanami
|
|
9
|
+
class Slice
|
|
10
|
+
module Routing
|
|
11
|
+
# @since 2.0.0
|
|
12
|
+
# @api private
|
|
13
|
+
module Middleware
|
|
14
|
+
# Wraps a rack app with a middleware stack
|
|
15
|
+
#
|
|
16
|
+
# We use this class to add middlewares to the rack application generated from
|
|
17
|
+
# {Hanami::Slice::Router}.
|
|
18
|
+
#
|
|
19
|
+
# ```
|
|
20
|
+
# stack = Hanami::Slice::Routing::Middleware::Stack.new
|
|
21
|
+
# stack.use(Rack::ContentType, "text/html")
|
|
22
|
+
# stack.to_rack_app(a_rack_app)
|
|
23
|
+
# ```
|
|
24
|
+
#
|
|
25
|
+
# Middlewares can be mounted on specific paths:
|
|
26
|
+
#
|
|
27
|
+
# ```
|
|
28
|
+
# stack.with("/api") do
|
|
29
|
+
# stack.use(Rack::ContentType, "application/json")
|
|
30
|
+
# end
|
|
31
|
+
# ```
|
|
32
|
+
#
|
|
33
|
+
# @see Hanami::Config#middleware
|
|
34
|
+
#
|
|
35
|
+
# @since 2.0.0
|
|
36
|
+
# @api private
|
|
37
|
+
class Stack
|
|
38
|
+
include Enumerable
|
|
39
|
+
|
|
40
|
+
# @since 2.0.0
|
|
41
|
+
# @api private
|
|
42
|
+
attr_reader :stack
|
|
43
|
+
|
|
44
|
+
# Returns an array of Ruby namespaces from which to load middleware classes specified by
|
|
45
|
+
# symbol names given to {#use}.
|
|
46
|
+
#
|
|
47
|
+
# Defaults to `[Hanami::Middleware]`.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<Object>]
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
# @since 2.0.0
|
|
53
|
+
attr_reader :namespaces
|
|
54
|
+
|
|
55
|
+
# @since 2.0.0
|
|
56
|
+
# @api private
|
|
57
|
+
def initialize
|
|
58
|
+
@stack = Hash.new { |hash, key| hash[key] = [] }
|
|
59
|
+
@namespaces = [Hanami::Middleware]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @since 2.0.0
|
|
63
|
+
# @api private
|
|
64
|
+
def initialize_copy(source)
|
|
65
|
+
super
|
|
66
|
+
@stack = stack.dup
|
|
67
|
+
@namespaces = namespaces.dup
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Adds a middleware to the stack.
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# # Using a symbol name; adds Hanami::Middleware::BodyParser.new([:json])
|
|
74
|
+
# middleware.use :body_parser, :json
|
|
75
|
+
#
|
|
76
|
+
# # Using a class name
|
|
77
|
+
# middleware.use MyMiddleware
|
|
78
|
+
#
|
|
79
|
+
# # Adding a middleware before or after others
|
|
80
|
+
# middleware.use MyMiddleware, before: SomeMiddleware
|
|
81
|
+
# middleware.use MyMiddleware, after: OtherMiddleware
|
|
82
|
+
#
|
|
83
|
+
# @param spec [Symbol, Class] the middleware name or class name
|
|
84
|
+
# @param args [Array, nil] Arguments to pass to the middleware's `.new` method
|
|
85
|
+
# @param before [Class, nil] an optional (already added) middleware class to add the
|
|
86
|
+
# middleware before
|
|
87
|
+
# @param after [Class, nil] an optional (already added) middleware class to add the
|
|
88
|
+
# middleware after
|
|
89
|
+
#
|
|
90
|
+
# @return [self]
|
|
91
|
+
#
|
|
92
|
+
# @api public
|
|
93
|
+
# @since 2.0.0
|
|
94
|
+
def use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, **kwargs, &blk)
|
|
95
|
+
middleware = resolve_middleware_class(spec)
|
|
96
|
+
item = [middleware, args, kwargs, blk]
|
|
97
|
+
|
|
98
|
+
if before
|
|
99
|
+
@stack[path_prefix].insert((idx = index_of(before, path_prefix)).zero? ? 0 : idx - 1, item)
|
|
100
|
+
elsif after
|
|
101
|
+
@stack[path_prefix].insert(index_of(after, path_prefix) + 1, item)
|
|
102
|
+
else
|
|
103
|
+
@stack[path_prefix].push(item)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @since 2.0.0
|
|
110
|
+
# @api private
|
|
111
|
+
def update(other)
|
|
112
|
+
other.stack.each do |path_prefix, items|
|
|
113
|
+
stack[path_prefix].concat(items)
|
|
114
|
+
end
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @since 2.0.0
|
|
119
|
+
# @api private
|
|
120
|
+
def to_rack_app(app)
|
|
121
|
+
unless Hanami.bundled?("rack")
|
|
122
|
+
raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
mapping = to_hash
|
|
126
|
+
return app if mapping.empty?
|
|
127
|
+
|
|
128
|
+
Hanami::Middleware::App.new(app, mapping)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @since 2.0.0
|
|
132
|
+
# @api private
|
|
133
|
+
def to_hash
|
|
134
|
+
@stack.each_with_object({}) do |(path, _), result|
|
|
135
|
+
result[path] = stack_for(path)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @since 2.0.0
|
|
140
|
+
# @api private
|
|
141
|
+
def empty?
|
|
142
|
+
@stack.empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @since 2.0.0
|
|
146
|
+
# @api private
|
|
147
|
+
def each(&blk)
|
|
148
|
+
@stack.each(&blk)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @since 2.0.0
|
|
152
|
+
# @api private
|
|
153
|
+
def mapped(builder, prefix, &blk)
|
|
154
|
+
if prefix == ::Hanami::Router::DEFAULT_PREFIX
|
|
155
|
+
builder.instance_eval(&blk)
|
|
156
|
+
else
|
|
157
|
+
builder.map(prefix, &blk)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
# @since 2.0.0
|
|
164
|
+
def index_of(middleware, path_prefix)
|
|
165
|
+
@stack[path_prefix].index { |(m, *)| m.equal?(middleware) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @since 2.0.0
|
|
169
|
+
# @api private
|
|
170
|
+
def stack_for(current_path)
|
|
171
|
+
@stack.each_with_object([]) do |(path, stack), result|
|
|
172
|
+
next unless current_path.start_with?(path)
|
|
173
|
+
|
|
174
|
+
result.push(stack)
|
|
175
|
+
end.flatten(1)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @since 2.0.0
|
|
179
|
+
def resolve_middleware_class(spec)
|
|
180
|
+
case spec
|
|
181
|
+
when Symbol then load_middleware_class(spec)
|
|
182
|
+
when Class, Module then spec
|
|
183
|
+
else
|
|
184
|
+
if spec.respond_to?(:call)
|
|
185
|
+
spec
|
|
186
|
+
else
|
|
187
|
+
raise UnsupportedMiddlewareSpecError, spec
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# @since 2.0.0
|
|
193
|
+
def load_middleware_class(spec)
|
|
194
|
+
begin
|
|
195
|
+
require "hanami/middleware/#{spec}"
|
|
196
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# FIXME: Classify must use App inflector
|
|
200
|
+
class_name = Hanami::Utils::String.classify(spec.to_s)
|
|
201
|
+
namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }
|
|
202
|
+
|
|
203
|
+
if namespace
|
|
204
|
+
namespace.const_get(class_name)
|
|
205
|
+
else
|
|
206
|
+
raise(
|
|
207
|
+
UnsupportedMiddlewareSpecError,
|
|
208
|
+
"Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Slice
|
|
5
|
+
# @api private
|
|
6
|
+
module Routing
|
|
7
|
+
# Hanami app router endpoint resolver
|
|
8
|
+
#
|
|
9
|
+
# This resolves endpoints objects from a slice container using the strings passed to `to:` as
|
|
10
|
+
# their container keys.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
# @since 2.0.0
|
|
14
|
+
class Resolver
|
|
15
|
+
SLICE_ACTIONS_KEY_NAMESPACE = "actions"
|
|
16
|
+
|
|
17
|
+
# @api private
|
|
18
|
+
# @since 2.0.0
|
|
19
|
+
def initialize(slice:)
|
|
20
|
+
@slice = slice
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @api private
|
|
24
|
+
# @since 2.0.0
|
|
25
|
+
def find_slice(slice_name)
|
|
26
|
+
slice.slices[slice_name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api private
|
|
30
|
+
# @since 2.0.0
|
|
31
|
+
def to_slice(slice_name)
|
|
32
|
+
self.class.new(slice: find_slice(slice_name))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
# @since 2.0.0
|
|
37
|
+
def call(_path, endpoint)
|
|
38
|
+
endpoint =
|
|
39
|
+
case endpoint
|
|
40
|
+
when String
|
|
41
|
+
resolve_slice_action(endpoint)
|
|
42
|
+
when Class
|
|
43
|
+
endpoint.respond_to?(:call) ? endpoint : endpoint.new
|
|
44
|
+
else
|
|
45
|
+
endpoint
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless endpoint.respond_to?(:call)
|
|
49
|
+
raise Routes::NotCallableEndpointError.new(endpoint)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
endpoint
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
# @since 2.0.0
|
|
59
|
+
attr_reader :slice
|
|
60
|
+
|
|
61
|
+
# @api private
|
|
62
|
+
# @since 2.0.0
|
|
63
|
+
def resolve_slice_action(key)
|
|
64
|
+
action_key = "#{SLICE_ACTIONS_KEY_NAMESPACE}.#{key}"
|
|
65
|
+
|
|
66
|
+
ensure_action_in_slice(action_key)
|
|
67
|
+
|
|
68
|
+
# Lazily resolve action from the slice to reduce router initialization time, and
|
|
69
|
+
# circumvent endless loops from the action requiring access to router-related
|
|
70
|
+
# concerns (which may not be fully loaded at the time of reading the routes)
|
|
71
|
+
-> (*args) {
|
|
72
|
+
action = slice.resolve(action_key) do
|
|
73
|
+
raise Routes::MissingActionError.new(action_key, slice)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
action.call(*args)
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ensure_action_in_slice(key)
|
|
81
|
+
return unless slice.booted?
|
|
82
|
+
|
|
83
|
+
raise Routes::MissingActionError.new(key, slice) unless slice.key?(key)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../constants"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
class Slice
|
|
7
|
+
# Infers a view name for automatically rendering within actions.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
# @since 2.0.0
|
|
11
|
+
class ViewNameInferrer
|
|
12
|
+
ALTERNATIVE_NAMES = {
|
|
13
|
+
"create" => "new",
|
|
14
|
+
"update" => "edit"
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# Returns an array of container keys for views matching the given action.
|
|
19
|
+
#
|
|
20
|
+
# Also provides alternative view keys for common RESTful actions.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# ViewNameInferrer.call(action_name: "Main::Actions::Posts::Create", slice: Main::Slice)
|
|
24
|
+
# # => ["views.posts.create", "views.posts.new"]
|
|
25
|
+
#
|
|
26
|
+
# @param action_class_name [String] action class name
|
|
27
|
+
# @param slice [Hanami::Slice, Hanami::Application] Hanami slice containing the action
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<string>] array of paired view container keys
|
|
30
|
+
def call(action_class_name:, slice:)
|
|
31
|
+
action_key_base = slice.config.actions.name_inference_base
|
|
32
|
+
view_key_base = slice.config.actions.view_name_inference_base
|
|
33
|
+
|
|
34
|
+
action_name_key = action_name_key(action_class_name, slice, action_key_base)
|
|
35
|
+
|
|
36
|
+
view_key = [view_key_base, action_name_key].compact.join(CONTAINER_KEY_DELIMITER)
|
|
37
|
+
|
|
38
|
+
[view_key, alternative_view_key(view_key)].compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def action_name_key(action_name, slice, key_base)
|
|
44
|
+
slice
|
|
45
|
+
.inflector
|
|
46
|
+
.underscore(action_name)
|
|
47
|
+
.sub(%r{^#{slice.slice_name.path}#{PATH_DELIMITER}}, "")
|
|
48
|
+
.sub(%r{^#{key_base}#{PATH_DELIMITER}}, "")
|
|
49
|
+
.gsub("/", CONTAINER_KEY_DELIMITER)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def alternative_view_key(view_key)
|
|
53
|
+
parts = view_key.split(CONTAINER_KEY_DELIMITER)
|
|
54
|
+
|
|
55
|
+
alternative_name = ALTERNATIVE_NAMES[parts.last]
|
|
56
|
+
return unless alternative_name
|
|
57
|
+
|
|
58
|
+
[parts[0..-2], alternative_name].join(CONTAINER_KEY_DELIMITER)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|