hanami 2.0.0.alpha5 → 2.0.0.alpha7.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 +4 -4
- data/CHANGELOG.md +141 -0
- data/README.md +2 -6
- data/hanami.gemspec +5 -4
- data/lib/hanami/application/container/{boot → providers}/inflector.rb +1 -1
- data/lib/hanami/application/container/{boot → providers}/logger.rb +1 -1
- data/lib/hanami/application/container/providers/rack_logger.rb +15 -0
- data/lib/hanami/application/container/{boot → providers}/rack_monitor.rb +2 -2
- data/lib/hanami/application/container/{boot → providers}/routes_helper.rb +1 -1
- data/lib/hanami/application/container/{boot → providers}/settings.rb +1 -1
- data/lib/hanami/application/routing/router.rb +1 -1
- data/lib/hanami/application/slice_registrar.rb +106 -0
- data/lib/hanami/application.rb +75 -142
- data/lib/hanami/cli/application/cli.rb +1 -1
- data/lib/hanami/configuration/actions.rb +90 -0
- data/lib/hanami/configuration.rb +4 -23
- data/lib/hanami/constants.rb +23 -0
- data/lib/hanami/errors.rb +12 -0
- data/lib/hanami/{init.rb → prepare.rb} +1 -1
- data/lib/hanami/slice.rb +192 -128
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +27 -51
- data/lib/hanami.rb +15 -7
- metadata +20 -16
- data/lib/hanami/application/autoloader/inflector_adapter.rb +0 -22
- data/lib/hanami/application/container/boot/rack_logger.rb +0 -19
data/lib/hanami/application.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/system/container"
|
4
|
-
require "dry/system/loader/autoloading"
|
5
4
|
require "hanami/configuration"
|
6
5
|
require "pathname"
|
7
6
|
require "rack"
|
8
7
|
require "zeitwerk"
|
8
|
+
require_relative "constants"
|
9
9
|
require_relative "slice"
|
10
|
-
require_relative "application/
|
10
|
+
require_relative "application/slice_registrar"
|
11
11
|
|
12
12
|
module Hanami
|
13
13
|
# Hanami application class
|
@@ -18,10 +18,13 @@ module Hanami
|
|
18
18
|
|
19
19
|
class << self
|
20
20
|
def inherited(klass)
|
21
|
+
super
|
21
22
|
@_mutex.synchronize do
|
22
23
|
klass.class_eval do
|
23
|
-
@_mutex
|
24
|
+
@_mutex = Mutex.new
|
24
25
|
@_configuration = Hanami::Configuration.new(application_name: name, env: Hanami.env)
|
26
|
+
@autoloader = Zeitwerk::Loader.new
|
27
|
+
@container = Class.new(Dry::System::Container)
|
25
28
|
|
26
29
|
extend ClassMethods
|
27
30
|
end
|
@@ -37,9 +40,11 @@ module Hanami
|
|
37
40
|
#
|
38
41
|
# rubocop:disable Metrics/ModuleLength
|
39
42
|
module ClassMethods
|
43
|
+
attr_reader :autoloader, :container
|
44
|
+
|
40
45
|
def self.extended(klass)
|
41
46
|
klass.class_eval do
|
42
|
-
@
|
47
|
+
@prepared = @booted = false
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
@@ -47,76 +52,50 @@ module Hanami
|
|
47
52
|
@_configuration
|
48
53
|
end
|
49
54
|
|
50
|
-
|
51
|
-
|
52
|
-
def init # rubocop:disable Metrics/MethodLength
|
53
|
-
return self if inited?
|
54
|
-
|
55
|
-
configuration.finalize!
|
56
|
-
|
57
|
-
@autoloader = Zeitwerk::Loader.new
|
58
|
-
autoloader.inflector = Autoloader::InflectorAdapter.new(inflector)
|
55
|
+
alias_method :config, :configuration
|
59
56
|
|
60
|
-
|
57
|
+
def prepare(provider_name = nil)
|
58
|
+
container.prepare(provider_name) and return self if provider_name
|
61
59
|
|
62
|
-
|
63
|
-
@deps_module = prepare_deps_module
|
60
|
+
return self if prepared?
|
64
61
|
|
65
|
-
|
66
|
-
slices.values.each(&:init)
|
67
|
-
slices.freeze
|
62
|
+
configuration.finalize!
|
68
63
|
|
69
|
-
|
64
|
+
prepare_all
|
70
65
|
|
71
|
-
@
|
66
|
+
@prepared = true
|
72
67
|
self
|
73
68
|
end
|
74
69
|
|
75
70
|
def boot(&block)
|
76
71
|
return self if booted?
|
77
72
|
|
78
|
-
|
73
|
+
prepare
|
79
74
|
|
80
75
|
container.finalize!(&block)
|
81
76
|
|
82
|
-
slices.
|
77
|
+
slices.each(&:boot)
|
83
78
|
|
84
79
|
@booted = true
|
85
80
|
self
|
86
81
|
end
|
87
82
|
|
88
83
|
def shutdown
|
84
|
+
slices.each(&:shutdown)
|
89
85
|
container.shutdown!
|
86
|
+
self
|
90
87
|
end
|
91
88
|
|
92
|
-
def
|
93
|
-
|
89
|
+
def prepared?
|
90
|
+
!!@prepared
|
94
91
|
end
|
95
92
|
|
96
93
|
def booted?
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
def autoloader
|
101
|
-
raise "Application not init'ed" unless defined?(@autoloader)
|
102
|
-
|
103
|
-
@autoloader
|
104
|
-
end
|
105
|
-
|
106
|
-
def container
|
107
|
-
raise "Application not init'ed" unless defined?(@container)
|
108
|
-
|
109
|
-
@container
|
110
|
-
end
|
111
|
-
|
112
|
-
def deps
|
113
|
-
raise "Application not init'ed" unless defined?(@deps_module)
|
114
|
-
|
115
|
-
@deps_module
|
94
|
+
!!@booted
|
116
95
|
end
|
117
96
|
|
118
97
|
def router
|
119
|
-
raise "Application not
|
98
|
+
raise "Application not yet prepared" unless prepared?
|
120
99
|
|
121
100
|
@_mutex.synchronize do
|
122
101
|
@_router ||= load_router
|
@@ -128,47 +107,39 @@ module Hanami
|
|
128
107
|
end
|
129
108
|
|
130
109
|
def slices
|
131
|
-
@slices ||=
|
132
|
-
end
|
133
|
-
|
134
|
-
def register_slice(name, **slice_args)
|
135
|
-
raise "Slice +#{name}+ already registered" if slices.key?(name.to_sym)
|
136
|
-
|
137
|
-
slice = Slice.new(self, name: name, **slice_args)
|
138
|
-
slice.namespace.const_set :Slice, slice if slice.namespace # rubocop:disable Style/SafeNavigation
|
139
|
-
slices[name.to_sym] = slice
|
110
|
+
@slices ||= SliceRegistrar.new(self)
|
140
111
|
end
|
141
112
|
|
142
|
-
def
|
143
|
-
|
113
|
+
def register_slice(...)
|
114
|
+
slices.register(...)
|
144
115
|
end
|
145
116
|
|
146
|
-
def
|
147
|
-
container.
|
117
|
+
def register(...)
|
118
|
+
container.register(...)
|
148
119
|
end
|
149
120
|
|
150
|
-
def
|
151
|
-
container.
|
121
|
+
def register_provider(...)
|
122
|
+
container.register_provider(...)
|
152
123
|
end
|
153
124
|
|
154
|
-
def
|
155
|
-
container.start(
|
125
|
+
def start(...)
|
126
|
+
container.start(...)
|
156
127
|
end
|
157
128
|
|
158
|
-
def key?(
|
159
|
-
container.key?(
|
129
|
+
def key?(...)
|
130
|
+
container.key?(...)
|
160
131
|
end
|
161
132
|
|
162
133
|
def keys
|
163
134
|
container.keys
|
164
135
|
end
|
165
136
|
|
166
|
-
def [](
|
167
|
-
container[
|
137
|
+
def [](...)
|
138
|
+
container.[](...)
|
168
139
|
end
|
169
140
|
|
170
|
-
def resolve(
|
171
|
-
container.resolve(
|
141
|
+
def resolve(...)
|
142
|
+
container.resolve(...)
|
172
143
|
end
|
173
144
|
|
174
145
|
def settings
|
@@ -201,10 +172,10 @@ module Hanami
|
|
201
172
|
|
202
173
|
# @api private
|
203
174
|
def component_provider(component)
|
204
|
-
raise "Hanami.application must be
|
175
|
+
raise "Hanami.application must be prepared before detecting providers" unless prepared?
|
205
176
|
|
206
|
-
#
|
207
|
-
providers = slices.
|
177
|
+
# e.g. [Admin, Main, MyApp]
|
178
|
+
providers = slices.to_a + [self]
|
208
179
|
|
209
180
|
component_class = component.is_a?(Class) ? component : component.class
|
210
181
|
component_name = component_class.name
|
@@ -221,94 +192,59 @@ module Hanami
|
|
221
192
|
$LOAD_PATH.unshift base_path unless $LOAD_PATH.include?(base_path)
|
222
193
|
end
|
223
194
|
|
224
|
-
def
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
195
|
+
def prepare_all
|
196
|
+
load_settings
|
197
|
+
prepare_container_plugins
|
198
|
+
prepare_container_base_config
|
199
|
+
prepare_container_consts
|
200
|
+
container.configured!
|
201
|
+
prepare_slices
|
202
|
+
# For the application, the autoloader must be prepared after the slices, since
|
203
|
+
# they'll be configuring the autoloader with their own dirs
|
204
|
+
prepare_autoloader
|
232
205
|
end
|
233
206
|
|
234
|
-
def
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
namespace.const_set :Container, Class.new(Dry::System::Container)
|
207
|
+
def prepare_container_plugins
|
208
|
+
container.use(:env, inferrer: -> { Hanami.env })
|
209
|
+
container.use(:zeitwerk, loader: autoloader, run_setup: false, eager_load: false)
|
210
|
+
container.use(:notifications)
|
239
211
|
end
|
240
212
|
|
241
|
-
|
242
|
-
|
243
|
-
container.
|
244
|
-
container.use :notifications
|
245
|
-
|
246
|
-
container.configure do |config|
|
247
|
-
config.inflector = configuration.inflector
|
248
|
-
|
249
|
-
config.root = configuration.root
|
250
|
-
config.bootable_dirs = [
|
251
|
-
"config/boot",
|
252
|
-
Pathname(__dir__).join("application/container/boot").realpath,
|
253
|
-
]
|
213
|
+
def prepare_container_base_config
|
214
|
+
container.config.root = configuration.root
|
215
|
+
container.config.inflector = configuration.inflector
|
254
216
|
|
255
|
-
|
256
|
-
config
|
257
|
-
|
217
|
+
container.config.provider_dirs = [
|
218
|
+
"config/providers",
|
219
|
+
Pathname(__dir__).join("application/container/providers").realpath,
|
220
|
+
]
|
221
|
+
end
|
258
222
|
|
223
|
+
def prepare_autoload_paths
|
259
224
|
# Autoload classes defined in lib/[app_namespace]/
|
260
225
|
if root.join("lib", namespace_path).directory?
|
261
226
|
autoloader.push_dir(root.join("lib", namespace_path), namespace: namespace)
|
262
227
|
end
|
263
|
-
|
264
|
-
# Add lib/ to to the $LOAD_PATH so other files there (outside the app namespace)
|
265
|
-
# are require-able
|
266
|
-
container.add_to_load_path!("lib") if root.join("lib").directory?
|
267
|
-
|
268
|
-
container
|
269
228
|
end
|
270
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
271
229
|
|
272
|
-
def
|
273
|
-
|
274
|
-
namespace.const_get :Deps
|
275
|
-
rescue LoadError, NameError
|
230
|
+
def prepare_container_consts
|
231
|
+
namespace.const_set :Container, container
|
276
232
|
namespace.const_set :Deps, container.injector
|
277
233
|
end
|
278
234
|
|
279
|
-
def
|
280
|
-
|
281
|
-
|
282
|
-
.each(&method(:load_slice))
|
283
|
-
end
|
284
|
-
|
285
|
-
def slices_path
|
286
|
-
File.join(root, config.slices_dir)
|
235
|
+
def prepare_slices
|
236
|
+
slices.load_slices.each(&:prepare)
|
237
|
+
slices.freeze
|
287
238
|
end
|
288
239
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
slice_name = slice_path.relative_path_from(Pathname(slices_path)).to_s
|
294
|
-
slice_const_name = inflector.camelize(slice_name)
|
295
|
-
|
296
|
-
if config.slices_namespace.const_defined?(slice_const_name)
|
297
|
-
slice_module = config.slices_namespace.const_get(slice_const_name)
|
298
|
-
|
299
|
-
raise "Cannot use slice +#{slice_const_name}+ since it is not a module" unless slice_module.is_a?(Module)
|
300
|
-
else
|
301
|
-
slice_module = Module.new
|
302
|
-
config.slices_namespace.const_set inflector.camelize(slice_name), slice_module
|
240
|
+
def prepare_autoloader
|
241
|
+
# Autoload classes defined in lib/[app_namespace]/
|
242
|
+
if root.join("lib", namespace_path).directory?
|
243
|
+
autoloader.push_dir(root.join("lib", namespace_path), namespace: namespace)
|
303
244
|
end
|
304
245
|
|
305
|
-
|
306
|
-
slice_name,
|
307
|
-
namespace: slice_module,
|
308
|
-
root: slice_path.realpath
|
309
|
-
)
|
246
|
+
autoloader.setup
|
310
247
|
end
|
311
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
312
248
|
|
313
249
|
def load_settings
|
314
250
|
require_relative "application/settings"
|
@@ -321,9 +257,6 @@ module Hanami
|
|
321
257
|
Settings.new
|
322
258
|
end
|
323
259
|
|
324
|
-
MODULE_DELIMITER = "::"
|
325
|
-
private_constant :MODULE_DELIMITER
|
326
|
-
|
327
260
|
def autodiscover_application_constant(constants)
|
328
261
|
inflector.constantize([namespace_name, *constants].join(MODULE_DELIMITER))
|
329
262
|
end
|
@@ -26,7 +26,7 @@ module Hanami
|
|
26
26
|
# of the boot process respect the provided env
|
27
27
|
ENV["HANAMI_ENV"] = arguments[:env] if arguments[:env]
|
28
28
|
|
29
|
-
require "hanami/
|
29
|
+
require "hanami/prepare"
|
30
30
|
application = Hanami.application
|
31
31
|
|
32
32
|
[command.with_application(application), arguments]
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require_relative "application_configuration/cookies"
|
4
|
+
# require_relative "application_configuration/sessions"
|
5
|
+
# require_relative "application_configuration/content_security_policy"
|
6
|
+
# require_relative "configuration"
|
7
|
+
# require_relative "view_name_inferrer"
|
8
|
+
|
9
|
+
module Hanami
|
10
|
+
class Configuration
|
11
|
+
class Actions
|
12
|
+
include Dry::Configurable
|
13
|
+
|
14
|
+
setting :cookies, default: {}, constructor: -> options { Cookies.new(options) }
|
15
|
+
setting :sessions, constructor: proc { |storage, *options| Sessions.new(storage, *options) }
|
16
|
+
setting :csrf_protection
|
17
|
+
|
18
|
+
setting :name_inference_base, default: "actions"
|
19
|
+
setting :view_context_identifier, default: "view.context"
|
20
|
+
setting :view_name_inferrer, default: ViewNameInferrer
|
21
|
+
setting :view_name_inference_base, default: "views"
|
22
|
+
|
23
|
+
attr_accessor :content_security_policy
|
24
|
+
|
25
|
+
def initialize(*, **options)
|
26
|
+
super()
|
27
|
+
|
28
|
+
@base_configuration = Configuration.new
|
29
|
+
@content_security_policy = ContentSecurityPolicy.new do |csp|
|
30
|
+
if assets_server_url = options[:assets_server_url]
|
31
|
+
csp[:script_src] += " #{assets_server_url}"
|
32
|
+
csp[:style_src] += " #{assets_server_url}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
configure_defaults
|
37
|
+
end
|
38
|
+
|
39
|
+
def finalize!
|
40
|
+
# A nil value for `csrf_protection` means it has not been explicitly configured
|
41
|
+
# (neither true nor false), so we can default it to whether sessions are enabled
|
42
|
+
self.csrf_protection = sessions.enabled? if csrf_protection.nil?
|
43
|
+
|
44
|
+
if self.content_security_policy
|
45
|
+
self.default_headers["Content-Security-Policy"] = self.content_security_policy.to_str
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the list of available settings
|
50
|
+
#
|
51
|
+
# @return [Set]
|
52
|
+
#
|
53
|
+
# @since 2.0.0
|
54
|
+
# @api private
|
55
|
+
def settings
|
56
|
+
base_configuration.settings + self.class.settings
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_reader :base_configuration
|
62
|
+
|
63
|
+
# Apply defaults for base configuration settings
|
64
|
+
def configure_defaults
|
65
|
+
self.default_request_format = :html
|
66
|
+
self.default_response_format = :html
|
67
|
+
|
68
|
+
self.default_headers = {
|
69
|
+
"X-Frame-Options" => "DENY",
|
70
|
+
"X-Content-Type-Options" => "nosniff",
|
71
|
+
"X-XSS-Protection" => "1; mode=block"
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(name, *args, &block)
|
76
|
+
if config.respond_to?(name)
|
77
|
+
config.public_send(name, *args, &block)
|
78
|
+
elsif base_configuration.respond_to?(name)
|
79
|
+
base_configuration.public_send(name, *args, &block)
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def respond_to_missing?(name, _incude_all = false)
|
86
|
+
config.respond_to?(name) || base_configuration.respond_to?(name) || super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/hanami/configuration.rb
CHANGED
@@ -13,6 +13,7 @@ require_relative "configuration/middleware"
|
|
13
13
|
require_relative "configuration/router"
|
14
14
|
require_relative "configuration/sessions"
|
15
15
|
require_relative "configuration/source_dirs"
|
16
|
+
require_relative "constants"
|
16
17
|
|
17
18
|
module Hanami
|
18
19
|
# Hanami application configuration
|
@@ -26,8 +27,7 @@ module Hanami
|
|
26
27
|
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
27
28
|
private_constant :DEFAULT_ENVIRONMENTS
|
28
29
|
|
29
|
-
|
30
|
-
private_constant :MODULE_DELIMITER
|
30
|
+
attr_reader :env
|
31
31
|
|
32
32
|
attr_reader :actions
|
33
33
|
attr_reader :middleware
|
@@ -41,7 +41,7 @@ module Hanami
|
|
41
41
|
@namespace = application_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
|
42
42
|
|
43
43
|
@environments = DEFAULT_ENVIRONMENTS.clone
|
44
|
-
|
44
|
+
@env = env
|
45
45
|
|
46
46
|
# Some default setting values must be assigned at initialize-time to ensure they
|
47
47
|
# have appropriate values for the current application
|
@@ -117,16 +117,9 @@ module Hanami
|
|
117
117
|
inflector.underscore(@namespace).to_sym
|
118
118
|
end
|
119
119
|
|
120
|
-
setting :env
|
121
|
-
|
122
|
-
def env=(new_env)
|
123
|
-
config.env = env
|
124
|
-
apply_env_config(new_env)
|
125
|
-
end
|
126
|
-
|
127
120
|
setting :root, constructor: -> path { Pathname(path) }
|
128
121
|
|
129
|
-
setting :inflector, default: Dry::Inflector.new
|
122
|
+
setting :inflector, default: Dry::Inflector.new
|
130
123
|
|
131
124
|
def inflections(&block)
|
132
125
|
self.inflector = Dry::Inflector.new(&block)
|
@@ -148,20 +141,8 @@ module Hanami
|
|
148
141
|
|
149
142
|
setting :settings_store, default: Application::Settings::DotenvStore
|
150
143
|
|
151
|
-
setting :slices_dir, default: "slices"
|
152
|
-
|
153
|
-
setting :slices_namespace, default: Object
|
154
|
-
|
155
|
-
# TODO: convert into a dedicated object with explicit behaviour around blocks per
|
156
|
-
# slice, etc.
|
157
|
-
setting :slices, default: {}, constructor: :dup.to_proc
|
158
|
-
|
159
144
|
setting :source_dirs, default: Configuration::SourceDirs.new, cloneable: true
|
160
145
|
|
161
|
-
def slice(slice_name, &block)
|
162
|
-
slices[slice_name] = block
|
163
|
-
end
|
164
|
-
|
165
146
|
setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) }
|
166
147
|
|
167
148
|
def for_each_middleware(&blk)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
# @api private
|
5
|
+
MODULE_DELIMITER = "::"
|
6
|
+
private_constant :MODULE_DELIMITER
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
CONFIG_DIR = "config"
|
10
|
+
private_constant :CONFIG_DIR
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
SLICES_DIR = "slices"
|
14
|
+
private_constant :SLICES_DIR
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
LIB_DIR = "lib"
|
18
|
+
private_constant :LIB_DIR
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
RB_EXT = ".rb"
|
22
|
+
private_constant :RB_EXT
|
23
|
+
end
|