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.
@@ -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/autoloader/inflector_adapter"
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 = Mutex.new
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
- @inited = @booted = false
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
- alias config configuration
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
- load_settings
57
+ def prepare(provider_name = nil)
58
+ container.prepare(provider_name) and return self if provider_name
61
59
 
62
- @container = prepare_container
63
- @deps_module = prepare_deps_module
60
+ return self if prepared?
64
61
 
65
- load_slices
66
- slices.values.each(&:init)
67
- slices.freeze
62
+ configuration.finalize!
68
63
 
69
- autoloader.setup
64
+ prepare_all
70
65
 
71
- @inited = true
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
- init
73
+ prepare
79
74
 
80
75
  container.finalize!(&block)
81
76
 
82
- slices.values.each(&:boot)
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 inited?
93
- @inited
89
+ def prepared?
90
+ !!@prepared
94
91
  end
95
92
 
96
93
  def booted?
97
- @booted
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 init'ed" unless inited?
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 register(*args, **opts, &block)
143
- container.register(*args, **opts, &block)
113
+ def register_slice(...)
114
+ slices.register(...)
144
115
  end
145
116
 
146
- def register_bootable(*args, **opts, &block)
147
- container.boot(*args, **opts, &block)
117
+ def register(...)
118
+ container.register(...)
148
119
  end
149
120
 
150
- def init_bootable(*args)
151
- container.init(*args)
121
+ def register_provider(...)
122
+ container.register_provider(...)
152
123
  end
153
124
 
154
- def start_bootable(*args)
155
- container.start(*args)
125
+ def start(...)
126
+ container.start(...)
156
127
  end
157
128
 
158
- def key?(*args)
159
- container.key?(*args)
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 [](*args)
167
- container[*args]
137
+ def [](...)
138
+ container.[](...)
168
139
  end
169
140
 
170
- def resolve(*args)
171
- container.resolve(*args)
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 inited before detecting providers" unless inited?
175
+ raise "Hanami.application must be prepared before detecting providers" unless prepared?
205
176
 
206
- # [Admin, Main, MyApp] or [MyApp::Admin, MyApp::Main, MyApp]
207
- providers = slices.values + [self]
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 prepare_container
225
- define_container.tap do |container|
226
- configure_container container
227
- end
228
- end
229
-
230
- def prepare_deps_module
231
- define_deps_module
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 define_container
235
- require "#{application_name}/container"
236
- namespace.const_get :Container
237
- rescue LoadError, NameError
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
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
242
- def configure_container(container)
243
- container.use :env, inferrer: -> { Hanami.env }
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
- config.component_dirs.loader = Dry::System::Loader::Autoloading
256
- config.component_dirs.add_to_load_path = false
257
- end
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 define_deps_module
273
- require "#{application_name}/deps"
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 load_slices
280
- Dir[File.join(slices_path, "*")]
281
- .select(&File.method(:directory?))
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
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
290
- def load_slice(slice_path)
291
- slice_path = Pathname(slice_path)
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
- register_slice(
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/init"
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
@@ -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
- MODULE_DELIMITER = "::"
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
- config.env = env
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, cloneable: true
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # @since 2.0.0
5
+ Error = Class.new(StandardError)
6
+
7
+ # @since 2.0.0
8
+ ApplicationLoadError = Class.new(Error)
9
+
10
+ # @since 2.0.0
11
+ SliceLoadError = Class.new(Error)
12
+ end
@@ -2,4 +2,4 @@
2
2
 
3
3
  require_relative "setup"
4
4
 
5
- Hanami.init
5
+ Hanami.prepare