hanami 2.0.0.alpha6 → 2.0.0.alpha7
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 +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
|