hanami 2.0.0.alpha6 → 2.0.0.alpha7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/lib/hanami/application/routing/router.rb +36 -0
- data/lib/hanami/application/slice_registrar.rb +107 -0
- data/lib/hanami/application.rb +49 -106
- data/lib/hanami/boot/source_dirs.rb +44 -0
- data/lib/hanami/configuration/actions.rb +90 -0
- data/lib/hanami/configuration.rb +3 -22
- data/lib/hanami/constants.rb +23 -0
- data/lib/hanami/errors.rb +12 -0
- data/lib/hanami/slice.rb +157 -96
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +13 -5
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8a7910a3dd4b6e7d82711df4f181de1e8c63fbb91981542d2eb067d2cfe06b4
|
4
|
+
data.tar.gz: 2d09e35743119e1e630f196780df68cf7b7685c2d6e3208fe0c7804816ccb45f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51ecc6d32c036bfc2fae43498ca6f88bb937317e610d24e499a069b02ff4c8a49b253684ba4fe47d6f67e2c2fc2b0a989460fc1462b397ddd1a5ec539ace2b7c
|
7
|
+
data.tar.gz: aa3a1f17f9304605135391c3cb915f61cabbd5cbb2e4351e70be122353d36a85af979bbf4710ffdbfa27e1e77aea023f3de7ddda2b547399e1d96b396134da9c
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,65 @@
|
|
1
1
|
# Hanami
|
2
2
|
The web, with simplicity.
|
3
3
|
|
4
|
+
## v2.0.0.alpha7 - 2020-03-08
|
5
|
+
|
6
|
+
## Added
|
7
|
+
- [Tim Riley] Introduced `Hanami::ApplicationLoadError` and `Hanami::SliceLoadError` exceptions to represent errors encountered during application and slice loading.
|
8
|
+
- [Tim Riley] `Hanami::Slice.shutdown` can be used to stop all the providers in a slice
|
9
|
+
|
10
|
+
## Changed
|
11
|
+
- [Tim Riley] Slices are now represented as concrete classes (such as `Main::Slice`) inheriting from `Hanami::Slice`, as opposed to _instances_ of `Hanami::Slice`. You may create your own definitions for these slices in `config/slices/[slice_name].rb`, which you can then use for customising per-slice config and behavior, e.g.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# config/slices/main.rb:
|
15
|
+
|
16
|
+
module Main
|
17
|
+
class Slice < Hanami::Slice
|
18
|
+
# slice config here
|
19
|
+
end
|
20
|
+
end
|
21
|
+
```
|
22
|
+
- [Tim Riley] Application-level `config.slice(slice_name, &block)` setting has been removed in favour of slice configuration within concrete slice class definitions
|
23
|
+
- [Tim Riley] You can configure your slice imports inside your slice classes, e.g.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# config/slices/main.rb:
|
27
|
+
|
28
|
+
module Main
|
29
|
+
class Slice < Hanami::Slice
|
30
|
+
# Import all exported components from "search" slice
|
31
|
+
import from: :search
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
- [Tim Riley] You can configure your slice exports inside your slice classes, e.g.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# config/slices/search.rb:
|
39
|
+
|
40
|
+
module Search
|
41
|
+
class Slice < Hanami::Slice
|
42
|
+
# Export the "index_entity" component only
|
43
|
+
export ["index_entity"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
- [Tim Riley] For advanced cases, you can configure your slice's container via a `prepare_container` block:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# config/slices/search.rb:
|
51
|
+
|
52
|
+
module Search
|
53
|
+
class Slice < Hanami::Slice
|
54
|
+
prepare_container do |container|
|
55
|
+
# `container` object is available here, with
|
56
|
+
# slice-specific configuration already applied
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
- [Tim Riley] `Hanami::Application.shutdown` will now also shutdown all registered slices
|
62
|
+
|
4
63
|
## v2.0.0.alpha6 - 2022-02-10
|
5
64
|
### Added
|
6
65
|
- [Luca Guidi] Official support for Ruby: MRI 3.1
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
|
3
|
+
# require "hanami/application/router"
|
4
|
+
|
5
|
+
# Hanami.application.register_provider :router do
|
6
|
+
# start do
|
7
|
+
# configuration = Hanami.application.configuration
|
8
|
+
|
9
|
+
# routes = begin
|
10
|
+
# require File.join(configuration.root, configuration.router.routes_path)
|
11
|
+
# routes_class = Hanami.application.send(:autodiscover_application_constant, configuration.router.routes_class_name) # WIP private
|
12
|
+
# routes_class.routes
|
13
|
+
# rescue LoadError
|
14
|
+
# proc {}
|
15
|
+
# end
|
16
|
+
|
17
|
+
# resolver = configuration.router.resolver.new(
|
18
|
+
# slices: Hanami.application.slices,
|
19
|
+
# inflector: Hanami.application.inflector # TODO: use container[:inflector]?
|
20
|
+
# )
|
21
|
+
|
22
|
+
# router = Hanami::Application::Router.new(
|
23
|
+
# routes: routes,
|
24
|
+
# resolver: resolver,
|
25
|
+
# **configuration.router.options,
|
26
|
+
# ) do
|
27
|
+
# use Hanami.application[:rack_monitor]
|
28
|
+
|
29
|
+
# Hanami.application.config.for_each_middleware do |m, *args, &block|
|
30
|
+
# use(m, *args, &block)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
|
34
|
+
# register :router, router
|
35
|
+
# end
|
36
|
+
# end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../constants"
|
4
|
+
require_relative "../slice"
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
class Application
|
8
|
+
# @api private
|
9
|
+
class SliceRegistrar
|
10
|
+
attr_reader :application, :slices
|
11
|
+
private :application, :slices
|
12
|
+
|
13
|
+
def initialize(application)
|
14
|
+
@application = application
|
15
|
+
@slices = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def register(name, slice_class = nil, &block)
|
19
|
+
if slices.key?(name.to_sym)
|
20
|
+
raise SliceLoadError, "Slice '#{name}' is already registered"
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO: raise error unless name meets format (i.e. single level depth only)
|
24
|
+
|
25
|
+
slices[name.to_sym] = slice_class || build_slice(name, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](name)
|
29
|
+
slices.fetch(name) do
|
30
|
+
raise SliceLoadError, "Slice '#{name}' not found"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def freeze
|
35
|
+
slices.freeze
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_slices
|
40
|
+
slice_configs = Dir[root.join(CONFIG_DIR, "slices", "*#{RB_EXT}")]
|
41
|
+
.map { |file| File.basename(file, RB_EXT) }
|
42
|
+
|
43
|
+
slice_dirs = Dir[File.join(root, SLICES_DIR, "*")]
|
44
|
+
.select { |path| File.directory?(path) }
|
45
|
+
.map { |path| File.basename(path) }
|
46
|
+
|
47
|
+
(slice_dirs + slice_configs).uniq.sort.each do |slice_name|
|
48
|
+
load_slice(slice_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def each(&block)
|
55
|
+
slices.each_value(&block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_a
|
59
|
+
slices.values
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Attempts to load a slice class defined in `config/slices/[slice_name].rb`, then
|
65
|
+
# registers the slice with the matching class, if found.
|
66
|
+
def load_slice(slice_name)
|
67
|
+
slice_const_name = inflector.camelize(slice_name)
|
68
|
+
slice_require_path = root.join("config", "slices", slice_name).to_s
|
69
|
+
|
70
|
+
begin
|
71
|
+
require(slice_require_path)
|
72
|
+
rescue LoadError => e
|
73
|
+
raise e unless e.path == slice_require_path
|
74
|
+
end
|
75
|
+
|
76
|
+
slice_class =
|
77
|
+
begin
|
78
|
+
inflector.constantize("#{slice_const_name}::Slice")
|
79
|
+
rescue NameError => e
|
80
|
+
raise e unless e.name == :Slice
|
81
|
+
end
|
82
|
+
|
83
|
+
register(slice_name, slice_class)
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_slice(slice_name, &block)
|
87
|
+
slice_module =
|
88
|
+
begin
|
89
|
+
slice_module_name = inflector.camelize(slice_name.to_s)
|
90
|
+
inflector.constantize(slice_module_name)
|
91
|
+
rescue NameError
|
92
|
+
Object.const_set(inflector.camelize(slice_module_name), Module.new)
|
93
|
+
end
|
94
|
+
|
95
|
+
slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
|
96
|
+
end
|
97
|
+
|
98
|
+
def root
|
99
|
+
application.root
|
100
|
+
end
|
101
|
+
|
102
|
+
def inflector
|
103
|
+
application.inflector
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/hanami/application.rb
CHANGED
@@ -1,12 +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/slice_registrar"
|
10
11
|
|
11
12
|
module Hanami
|
12
13
|
# Hanami application class
|
@@ -20,8 +21,10 @@ module Hanami
|
|
20
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,6 +40,8 @@ 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
|
@@ -50,26 +55,13 @@ module Hanami
|
|
50
55
|
alias_method :config, :configuration
|
51
56
|
|
52
57
|
def prepare(provider_name = nil)
|
53
|
-
if provider_name
|
54
|
-
container.prepare(provider_name)
|
55
|
-
return self
|
56
|
-
end
|
58
|
+
container.prepare(provider_name) and return self if provider_name
|
57
59
|
|
58
60
|
return self if prepared?
|
59
61
|
|
60
62
|
configuration.finalize!
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
@autoloader = Zeitwerk::Loader.new
|
65
|
-
@container = prepare_container
|
66
|
-
@deps_module = prepare_deps_module
|
67
|
-
|
68
|
-
load_slices
|
69
|
-
slices.each_value(&:prepare)
|
70
|
-
slices.freeze
|
71
|
-
|
72
|
-
@autoloader.setup
|
64
|
+
prepare_all
|
73
65
|
|
74
66
|
@prepared = true
|
75
67
|
self
|
@@ -82,40 +74,24 @@ module Hanami
|
|
82
74
|
|
83
75
|
container.finalize!(&block)
|
84
76
|
|
85
|
-
slices.
|
77
|
+
slices.each(&:boot)
|
86
78
|
|
87
79
|
@booted = true
|
88
80
|
self
|
89
81
|
end
|
90
82
|
|
91
83
|
def shutdown
|
84
|
+
slices.each(&:shutdown)
|
92
85
|
container.shutdown!
|
86
|
+
self
|
93
87
|
end
|
94
88
|
|
95
89
|
def prepared?
|
96
|
-
|
90
|
+
!!@prepared
|
97
91
|
end
|
98
92
|
|
99
93
|
def booted?
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
def autoloader
|
104
|
-
raise "Application not yet prepared" unless defined?(@autoloader)
|
105
|
-
|
106
|
-
@autoloader
|
107
|
-
end
|
108
|
-
|
109
|
-
def container
|
110
|
-
raise "Application not yet prepared" unless defined?(@container)
|
111
|
-
|
112
|
-
@container
|
113
|
-
end
|
114
|
-
|
115
|
-
def deps
|
116
|
-
raise "Application not yet prepared" unless defined?(@deps_module)
|
117
|
-
|
118
|
-
@deps_module
|
94
|
+
!!@booted
|
119
95
|
end
|
120
96
|
|
121
97
|
def router
|
@@ -131,15 +107,11 @@ module Hanami
|
|
131
107
|
end
|
132
108
|
|
133
109
|
def slices
|
134
|
-
@slices ||=
|
110
|
+
@slices ||= SliceRegistrar.new(self)
|
135
111
|
end
|
136
112
|
|
137
|
-
def register_slice(
|
138
|
-
|
139
|
-
|
140
|
-
slice = Slice.new(self, name: name, **slice_args)
|
141
|
-
slice.namespace.const_set :Slice, slice if slice.namespace # rubocop:disable Style/SafeNavigation
|
142
|
-
slices[name.to_sym] = slice
|
113
|
+
def register_slice(...)
|
114
|
+
slices.register(...)
|
143
115
|
end
|
144
116
|
|
145
117
|
def register(...)
|
@@ -202,8 +174,8 @@ module Hanami
|
|
202
174
|
def component_provider(component)
|
203
175
|
raise "Hanami.application must be prepared before detecting providers" unless prepared?
|
204
176
|
|
205
|
-
#
|
206
|
-
providers = slices.
|
177
|
+
# e.g. [Admin, Main, MyApp]
|
178
|
+
providers = slices.to_a + [self]
|
207
179
|
|
208
180
|
component_class = component.is_a?(Class) ? component : component.class
|
209
181
|
component_name = component_class.name
|
@@ -220,20 +192,25 @@ module Hanami
|
|
220
192
|
$LOAD_PATH.unshift base_path unless $LOAD_PATH.include?(base_path)
|
221
193
|
end
|
222
194
|
|
223
|
-
|
224
|
-
|
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
|
205
|
+
end
|
232
206
|
|
233
|
-
|
234
|
-
container.use
|
235
|
-
container.use :
|
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)
|
211
|
+
end
|
236
212
|
|
213
|
+
def prepare_container_base_config
|
237
214
|
container.config.root = configuration.root
|
238
215
|
container.config.inflector = configuration.inflector
|
239
216
|
|
@@ -241,63 +218,32 @@ module Hanami
|
|
241
218
|
"config/providers",
|
242
219
|
Pathname(__dir__).join("application/container/providers").realpath,
|
243
220
|
]
|
221
|
+
end
|
244
222
|
|
223
|
+
def prepare_autoload_paths
|
245
224
|
# Autoload classes defined in lib/[app_namespace]/
|
246
225
|
if root.join("lib", namespace_path).directory?
|
247
|
-
|
226
|
+
autoloader.push_dir(root.join("lib", namespace_path), namespace: namespace)
|
248
227
|
end
|
249
|
-
|
250
|
-
# Add lib/ to to the $LOAD_PATH so any files there (outside the app namespace) can
|
251
|
-
# be required
|
252
|
-
container.add_to_load_path!("lib") if root.join("lib").directory?
|
253
|
-
|
254
|
-
container.configured!
|
255
|
-
|
256
|
-
container
|
257
|
-
end
|
258
|
-
# rubocop:enable Metrics/AbcSize
|
259
|
-
|
260
|
-
def prepare_deps_module
|
261
|
-
define_deps_module
|
262
228
|
end
|
263
229
|
|
264
|
-
def
|
265
|
-
|
266
|
-
namespace.const_get :Deps
|
267
|
-
rescue LoadError, NameError
|
230
|
+
def prepare_container_consts
|
231
|
+
namespace.const_set :Container, container
|
268
232
|
namespace.const_set :Deps, container.injector
|
269
233
|
end
|
270
234
|
|
271
|
-
def
|
272
|
-
|
273
|
-
|
274
|
-
.each(&method(:load_slice))
|
275
|
-
end
|
276
|
-
|
277
|
-
def slices_path
|
278
|
-
File.join(root, config.slices_dir)
|
235
|
+
def prepare_slices
|
236
|
+
slices.load_slices.each(&:prepare)
|
237
|
+
slices.freeze
|
279
238
|
end
|
280
239
|
|
281
|
-
def
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
slice_const_name = inflector.camelize(slice_name)
|
286
|
-
|
287
|
-
if config.slices_namespace.const_defined?(slice_const_name)
|
288
|
-
slice_module = config.slices_namespace.const_get(slice_const_name)
|
289
|
-
|
290
|
-
raise "Cannot use slice +#{slice_const_name}+ since it is not a module" unless slice_module.is_a?(Module)
|
291
|
-
else
|
292
|
-
slice_module = Module.new
|
293
|
-
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)
|
294
244
|
end
|
295
245
|
|
296
|
-
|
297
|
-
slice_name,
|
298
|
-
namespace: slice_module,
|
299
|
-
root: slice_path.realpath
|
300
|
-
)
|
246
|
+
autoloader.setup
|
301
247
|
end
|
302
248
|
|
303
249
|
def load_settings
|
@@ -311,9 +257,6 @@ module Hanami
|
|
311
257
|
Settings.new
|
312
258
|
end
|
313
259
|
|
314
|
-
MODULE_DELIMITER = "::"
|
315
|
-
private_constant :MODULE_DELIMITER
|
316
|
-
|
317
260
|
def autodiscover_application_constant(constants)
|
318
261
|
inflector.constantize([namespace_name, *constants].join(MODULE_DELIMITER))
|
319
262
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Hanami
|
2
|
+
module Boot
|
3
|
+
module SourceDirs
|
4
|
+
def self.setup_component_dir!(component_dir, slice, container)
|
5
|
+
# TODO: this `== "lib"` check should be codified into a method somewhere
|
6
|
+
if component_dir.path == "lib"
|
7
|
+
# Expect component files in the root of the lib
|
8
|
+
# component dir to define classes inside the slice's namespace.
|
9
|
+
#
|
10
|
+
# e.g. "lib/foo.rb" should define SliceNamespace::Foo, and will be
|
11
|
+
# registered as "foo"
|
12
|
+
component_dir.namespaces.root(key: nil, const: slice.namespace_path)
|
13
|
+
|
14
|
+
slice.application.autoloader.push_dir(slice.root.join("lib"), namespace: slice.namespace)
|
15
|
+
|
16
|
+
container.config.component_dirs.add(component_dir)
|
17
|
+
else
|
18
|
+
# Expect component files in the root of these component dirs to define
|
19
|
+
# classes inside a namespace matching the dir.
|
20
|
+
#
|
21
|
+
# e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, and
|
22
|
+
# will be registered as "actions.foo"
|
23
|
+
|
24
|
+
dir_namespace_path = File.join(slice.namespace_path, component_dir.path)
|
25
|
+
|
26
|
+
autoloader_namespace = begin
|
27
|
+
slice.inflector.constantize(slice.inflector.camelize(dir_namespace_path))
|
28
|
+
rescue NameError
|
29
|
+
slice.namespace.const_set(slice.inflector.camelize(component_dir.path), Module.new)
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: do we need to do something special to clear out any previously configured root namespace here?
|
33
|
+
component_dir.namespaces.root(const: dir_namespace_path, key: component_dir.path) # TODO: do we need to swap path delimiters for key delimiters here?
|
34
|
+
container.config.component_dirs.add(component_dir)
|
35
|
+
|
36
|
+
slice.application.autoloader.push_dir(
|
37
|
+
slice.root.join(component_dir.path),
|
38
|
+
namespace: autoloader_namespace
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -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,13 +117,6 @@ 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
122
|
setting :inflector, default: Dry::Inflector.new
|
@@ -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
|
data/lib/hanami/slice.rb
CHANGED
@@ -1,130 +1,190 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "dry/system/container"
|
4
|
+
require "hanami/errors"
|
4
5
|
require "pathname"
|
6
|
+
require_relative "constants"
|
5
7
|
|
6
8
|
module Hanami
|
7
9
|
# Distinct area of concern within an Hanami application
|
8
10
|
#
|
9
11
|
# @since 2.0.0
|
10
12
|
class Slice
|
11
|
-
|
12
|
-
|
13
|
-
def initialize(application, name:, namespace: nil, root: nil, container: nil)
|
14
|
-
@application = application
|
15
|
-
@name = name.to_sym
|
16
|
-
@namespace = namespace
|
17
|
-
@root = root ? Pathname(root) : root
|
18
|
-
@container = container
|
19
|
-
end
|
13
|
+
def self.inherited(klass)
|
14
|
+
super
|
20
15
|
|
21
|
-
|
22
|
-
application.inflector
|
23
|
-
end
|
16
|
+
klass.extend(ClassMethods)
|
24
17
|
|
25
|
-
|
26
|
-
|
18
|
+
# Eagerly initialize any variables that may be accessed inside the subclass body
|
19
|
+
klass.instance_variable_set(:@application, Hanami.application)
|
20
|
+
klass.instance_variable_set(:@container, Class.new(Dry::System::Container))
|
27
21
|
end
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
23
|
+
# rubocop:disable Metrics/ModuleLength
|
24
|
+
module ClassMethods
|
25
|
+
attr_reader :application, :container
|
26
|
+
|
27
|
+
def slice_name
|
28
|
+
inflector.underscore(name.split(MODULE_DELIMITER)[-2]).to_sym
|
33
29
|
end
|
34
30
|
|
35
|
-
|
31
|
+
def namespace
|
32
|
+
inflector.constantize(name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER))
|
33
|
+
end
|
36
34
|
|
37
|
-
|
35
|
+
def namespace_path
|
36
|
+
inflector.underscore(namespace)
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
def root
|
40
|
+
application.root.join(SLICES_DIR, slice_name.to_s)
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def inflector
|
44
|
+
application.inflector
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
47
|
+
def prepare(provider_name = nil)
|
48
|
+
container.prepare(provider_name) and return self if provider_name
|
48
49
|
|
49
|
-
|
50
|
-
container.finalize!
|
50
|
+
return self if prepared?
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
end
|
52
|
+
ensure_slice_name
|
53
|
+
ensure_slice_consts
|
55
54
|
|
56
|
-
|
57
|
-
def booted?
|
58
|
-
!!@booted
|
59
|
-
end
|
60
|
-
# rubocop:enable Style/DoubleNegation
|
55
|
+
prepare_all
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
@prepared = true
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare_container(&block)
|
62
|
+
@prepare_container_block = block
|
63
|
+
end
|
64
|
+
|
65
|
+
def boot
|
66
|
+
return self if booted?
|
67
|
+
|
68
|
+
container.finalize!
|
65
69
|
|
66
|
-
|
67
|
-
# TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
|
68
|
-
raise "Cannot import after booting" if booted?
|
70
|
+
@booted = true
|
69
71
|
|
70
|
-
|
71
|
-
slice_name = from
|
72
|
-
# TODO: better error than the KeyError from fetch if the slice doesn't exist
|
73
|
-
from = application.slices.fetch(from.to_sym).container
|
72
|
+
self
|
74
73
|
end
|
75
74
|
|
76
|
-
|
75
|
+
def shutdown
|
76
|
+
container.shutdown!
|
77
|
+
self
|
78
|
+
end
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
+
def prepared?
|
81
|
+
!!@prepared
|
82
|
+
end
|
80
83
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
+
def booted?
|
85
|
+
!!@booted
|
86
|
+
end
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
def register(...)
|
89
|
+
container.register(...)
|
90
|
+
end
|
88
91
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
+
def register_provider(...)
|
93
|
+
container.register_provider(...)
|
94
|
+
end
|
92
95
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
def start(...)
|
97
|
+
container.start(...)
|
98
|
+
end
|
96
99
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
+
def key?(...)
|
101
|
+
container.key?(...)
|
102
|
+
end
|
100
103
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
+
def keys
|
105
|
+
container.keys
|
106
|
+
end
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
+
def [](...)
|
109
|
+
container.[](...)
|
110
|
+
end
|
111
|
+
|
112
|
+
def resolve(...)
|
113
|
+
container.resolve(...)
|
114
|
+
end
|
115
|
+
|
116
|
+
def export(keys)
|
117
|
+
container.config.exports = keys
|
118
|
+
end
|
119
|
+
|
120
|
+
def import(from:, **kwargs)
|
121
|
+
# TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
|
122
|
+
raise "Cannot import after booting" if booted?
|
123
|
+
|
124
|
+
application = self.application
|
125
|
+
|
126
|
+
container.after(:configure) do
|
127
|
+
if from.is_a?(Symbol) || from.is_a?(String)
|
128
|
+
slice_name = from
|
129
|
+
from = application.slices[from.to_sym].container
|
130
|
+
end
|
131
|
+
|
132
|
+
as = kwargs[:as] || slice_name
|
133
|
+
|
134
|
+
import(from: from, as: as, **kwargs)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def ensure_slice_name
|
141
|
+
unless name
|
142
|
+
raise SliceLoadError, "Slice must have a class name before it can be prepared"
|
143
|
+
end
|
144
|
+
end
|
108
145
|
|
109
|
-
|
146
|
+
def ensure_slice_consts
|
147
|
+
if namespace.const_defined?(:Container) || namespace.const_defined?(:Deps)
|
148
|
+
raise(
|
149
|
+
SliceLoadError,
|
150
|
+
"#{namespace}::Container and #{namespace}::Deps constants must not already be defined"
|
151
|
+
)
|
152
|
+
end
|
153
|
+
end
|
110
154
|
|
111
|
-
|
112
|
-
|
113
|
-
|
155
|
+
def prepare_all
|
156
|
+
prepare_container_plugins
|
157
|
+
prepare_container_base_config
|
158
|
+
prepare_container_component_dirs
|
159
|
+
prepare_autoloader
|
160
|
+
prepare_container_imports
|
161
|
+
prepare_container_consts
|
162
|
+
instance_exec(container, &@prepare_container_block) if @prepare_container_block
|
163
|
+
container.configured!
|
164
|
+
end
|
114
165
|
|
115
|
-
|
116
|
-
|
117
|
-
loader: application.autoloader,
|
118
|
-
run_setup: false,
|
119
|
-
eager_load: false
|
166
|
+
def prepare_container_plugins
|
167
|
+
container.use(:env, inferrer: -> { Hanami.env })
|
120
168
|
|
121
|
-
|
122
|
-
|
123
|
-
|
169
|
+
container.use(
|
170
|
+
:zeitwerk,
|
171
|
+
loader: application.autoloader,
|
172
|
+
run_setup: false,
|
173
|
+
eager_load: false
|
174
|
+
)
|
175
|
+
end
|
124
176
|
|
125
|
-
|
177
|
+
def prepare_container_base_config
|
178
|
+
container.config.name = slice_name
|
126
179
|
container.config.root = root
|
127
|
-
container.config.provider_dirs = ["config
|
180
|
+
container.config.provider_dirs = [File.join("config", "providers")]
|
181
|
+
|
182
|
+
container.config.env = application.configuration.env
|
183
|
+
container.config.inflector = application.configuration.inflector
|
184
|
+
end
|
185
|
+
|
186
|
+
def prepare_container_component_dirs # rubocop:disable Metrics/AbcSize
|
187
|
+
return unless root&.directory?
|
128
188
|
|
129
189
|
# Add component dirs for each configured component path
|
130
190
|
application.configuration.source_dirs.component_dirs.each do |component_dir|
|
@@ -132,8 +192,7 @@ module Hanami
|
|
132
192
|
|
133
193
|
component_dir = component_dir.dup
|
134
194
|
|
135
|
-
|
136
|
-
if component_dir.path == "lib"
|
195
|
+
if component_dir.path == LIB_DIR
|
137
196
|
# Expect component files in the root of the lib/ component dir to define
|
138
197
|
# classes inside the slice's namespace.
|
139
198
|
#
|
@@ -141,8 +200,6 @@ module Hanami
|
|
141
200
|
# "foo"
|
142
201
|
component_dir.namespaces.delete_root
|
143
202
|
component_dir.namespaces.add_root(key: nil, const: namespace_path)
|
144
|
-
|
145
|
-
container.config.component_dirs.add(component_dir)
|
146
203
|
else
|
147
204
|
# Expect component files in the root of non-lib/ component dirs to define
|
148
205
|
# classes inside a namespace matching that dir.
|
@@ -154,13 +211,15 @@ module Hanami
|
|
154
211
|
|
155
212
|
component_dir.namespaces.delete_root
|
156
213
|
component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
|
157
|
-
|
158
|
-
container.config.component_dirs.add(component_dir)
|
159
214
|
end
|
215
|
+
|
216
|
+
container.config.component_dirs.add(component_dir)
|
160
217
|
end
|
161
218
|
end
|
162
219
|
|
163
|
-
|
220
|
+
def prepare_autoloader # rubocop:disable Metrics/AbcSize
|
221
|
+
return unless root&.directory?
|
222
|
+
|
164
223
|
# Pass configured autoload dirs to the autoloader
|
165
224
|
application.configuration.source_dirs.autoload_paths.each do |autoload_path|
|
166
225
|
next unless root.join(autoload_path).directory?
|
@@ -180,13 +239,15 @@ module Hanami
|
|
180
239
|
end
|
181
240
|
end
|
182
241
|
|
183
|
-
|
242
|
+
def prepare_container_imports
|
243
|
+
container.import from: application.container, as: :application
|
244
|
+
end
|
245
|
+
|
246
|
+
def prepare_container_consts
|
184
247
|
namespace.const_set :Container, container
|
185
248
|
namespace.const_set :Deps, container.injector
|
186
249
|
end
|
187
|
-
|
188
|
-
container
|
189
250
|
end
|
190
|
-
# rubocop:enable Metrics/
|
251
|
+
# rubocop:enable Metrics/ModuleLength
|
191
252
|
end
|
192
253
|
end
|
data/lib/hanami/version.rb
CHANGED
data/lib/hanami.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "hanami/application"
|
4
|
+
require_relative "hanami/errors"
|
5
|
+
require_relative "hanami/version"
|
6
|
+
|
7
|
+
|
3
8
|
# A complete web framework for Ruby
|
4
9
|
#
|
5
10
|
# @since 0.1.0
|
6
11
|
#
|
7
12
|
# @see http://hanamirb.org
|
8
13
|
module Hanami
|
9
|
-
require "hanami/version"
|
10
|
-
require "hanami/application"
|
11
|
-
|
12
14
|
@_mutex = Mutex.new
|
13
15
|
|
14
16
|
def self.application
|
15
17
|
@_mutex.synchronize do
|
16
|
-
|
18
|
+
unless defined?(@_application)
|
19
|
+
raise ApplicationLoadError,
|
20
|
+
"Hanami.application is not yet configured. " \
|
21
|
+
"You may need to `require \"hanami/setup\"` to load your config/application.rb file."
|
22
|
+
end
|
17
23
|
|
18
24
|
@_application
|
19
25
|
end
|
@@ -25,7 +31,9 @@ module Hanami
|
|
25
31
|
|
26
32
|
def self.application=(klass)
|
27
33
|
@_mutex.synchronize do
|
28
|
-
|
34
|
+
if defined?(@_application)
|
35
|
+
raise ApplicationLoadError, "Hanami.application is already configured."
|
36
|
+
end
|
29
37
|
|
30
38
|
@_application = klass unless klass.name.nil?
|
31
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.alpha7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -230,11 +230,14 @@ files:
|
|
230
230
|
- lib/hanami/application/routing/resolver.rb
|
231
231
|
- lib/hanami/application/routing/resolver/node.rb
|
232
232
|
- lib/hanami/application/routing/resolver/trie.rb
|
233
|
+
- lib/hanami/application/routing/router.rb
|
233
234
|
- lib/hanami/application/settings.rb
|
234
235
|
- lib/hanami/application/settings/dotenv_store.rb
|
236
|
+
- lib/hanami/application/slice_registrar.rb
|
235
237
|
- lib/hanami/assets/application_configuration.rb
|
236
238
|
- lib/hanami/assets/configuration.rb
|
237
239
|
- lib/hanami/boot.rb
|
240
|
+
- lib/hanami/boot/source_dirs.rb
|
238
241
|
- lib/hanami/cli/application/cli.rb
|
239
242
|
- lib/hanami/cli/application/command.rb
|
240
243
|
- lib/hanami/cli/application/commands.rb
|
@@ -244,12 +247,15 @@ files:
|
|
244
247
|
- lib/hanami/cli/commands/command.rb
|
245
248
|
- lib/hanami/cli/commands/server.rb
|
246
249
|
- lib/hanami/configuration.rb
|
250
|
+
- lib/hanami/configuration/actions.rb
|
247
251
|
- lib/hanami/configuration/logger.rb
|
248
252
|
- lib/hanami/configuration/middleware.rb
|
249
253
|
- lib/hanami/configuration/null_configuration.rb
|
250
254
|
- lib/hanami/configuration/router.rb
|
251
255
|
- lib/hanami/configuration/sessions.rb
|
252
256
|
- lib/hanami/configuration/source_dirs.rb
|
257
|
+
- lib/hanami/constants.rb
|
258
|
+
- lib/hanami/errors.rb
|
253
259
|
- lib/hanami/prepare.rb
|
254
260
|
- lib/hanami/server.rb
|
255
261
|
- lib/hanami/setup.rb
|