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.
data/lib/hanami/slice.rb CHANGED
@@ -1,189 +1,253 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
- require "dry/system/loader/autoloading"
4
+ require "hanami/errors"
5
5
  require "pathname"
6
+ require_relative "constants"
6
7
 
7
8
  module Hanami
8
9
  # Distinct area of concern within an Hanami application
9
10
  #
10
11
  # @since 2.0.0
11
12
  class Slice
12
- attr_reader :application, :name, :namespace, :root
13
-
14
- def initialize(application, name:, namespace: nil, root: nil, container: nil)
15
- @application = application
16
- @name = name.to_sym
17
- @namespace = namespace
18
- @root = root ? Pathname(root) : root
19
- @container = container || define_container
20
- end
13
+ def self.inherited(klass)
14
+ super
21
15
 
22
- def inflector
23
- application.inflector
24
- end
16
+ klass.extend(ClassMethods)
25
17
 
26
- def namespace_path
27
- @namespace_path ||= inflector.underscore(namespace.to_s)
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))
28
21
  end
29
22
 
30
- def init
31
- container.import application: application.container
23
+ # rubocop:disable Metrics/ModuleLength
24
+ module ClassMethods
25
+ attr_reader :application, :container
32
26
 
33
- slice_block = application.configuration.slices[name]
34
- instance_eval(&slice_block) if slice_block
35
- end
27
+ def slice_name
28
+ inflector.underscore(name.split(MODULE_DELIMITER)[-2]).to_sym
29
+ end
36
30
 
37
- def boot
38
- container.finalize! do
39
- container.config.env = application.container.config.env
31
+ def namespace
32
+ inflector.constantize(name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER))
40
33
  end
41
34
 
42
- @booted = true
43
- self
44
- end
35
+ def namespace_path
36
+ inflector.underscore(namespace)
37
+ end
45
38
 
46
- # rubocop:disable Style/DoubleNegation
47
- def booted?
48
- !!@booted
49
- end
50
- # rubocop:enable Style/DoubleNegation
39
+ def root
40
+ application.root.join(SLICES_DIR, slice_name.to_s)
41
+ end
51
42
 
52
- def container
53
- @container ||= define_container
54
- end
43
+ def inflector
44
+ application.inflector
45
+ end
46
+
47
+ def prepare(provider_name = nil)
48
+ container.prepare(provider_name) and return self if provider_name
49
+
50
+ return self if prepared?
55
51
 
56
- def import(*slice_names)
57
- raise "Cannot import after booting" if booted?
52
+ ensure_slice_name
53
+ ensure_slice_consts
58
54
 
59
- slice_names.each do |slice_name|
60
- container.import slice_name.to_sym => application.slices.fetch(slice_name.to_sym).container
55
+ prepare_all
56
+
57
+ @prepared = true
58
+ self
61
59
  end
62
- end
63
60
 
64
- def register(*args, &block)
65
- container.register(*args, &block)
66
- end
61
+ def prepare_container(&block)
62
+ @prepare_container_block = block
63
+ end
67
64
 
68
- def register_bootable(*args, &block)
69
- container.boot(*args, &block)
70
- end
65
+ def boot
66
+ return self if booted?
71
67
 
72
- def init_bootable(*args)
73
- container.init(*args)
74
- end
68
+ container.finalize!
75
69
 
76
- def start_bootable(*args)
77
- container.start(*args)
78
- end
70
+ @booted = true
79
71
 
80
- def key?(*args)
81
- container.key?(*args)
82
- end
72
+ self
73
+ end
83
74
 
84
- def keys
85
- container.keys
86
- end
75
+ def shutdown
76
+ container.shutdown!
77
+ self
78
+ end
87
79
 
88
- def [](*args)
89
- container[*args]
90
- end
80
+ def prepared?
81
+ !!@prepared
82
+ end
91
83
 
92
- def resolve(*args)
93
- container.resolve(*args)
94
- end
84
+ def booted?
85
+ !!@booted
86
+ end
95
87
 
96
- private
88
+ def register(...)
89
+ container.register(...)
90
+ end
97
91
 
98
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
99
- def define_container
100
- container = Class.new(Dry::System::Container)
101
- container.use :env
92
+ def register_provider(...)
93
+ container.register_provider(...)
94
+ end
102
95
 
103
- container.configure do |config|
104
- config.name = name
105
- config.inflector = application.configuration.inflector
96
+ def start(...)
97
+ container.start(...)
98
+ end
106
99
 
107
- config.component_dirs.loader = Dry::System::Loader::Autoloading
108
- config.component_dirs.add_to_load_path = false
100
+ def key?(...)
101
+ container.key?(...)
102
+ end
109
103
 
110
- if root&.directory?
111
- config.root = root
112
- config.bootable_dirs = ["config/boot"]
104
+ def keys
105
+ container.keys
106
+ end
113
107
 
114
- # Add component dirs for each configured component path
115
- application.configuration.source_dirs.component_dirs.each do |component_dir|
116
- next unless root.join(component_dir.path).directory?
108
+ def [](...)
109
+ container.[](...)
110
+ end
117
111
 
118
- component_dir = component_dir.dup
112
+ def resolve(...)
113
+ container.resolve(...)
114
+ end
119
115
 
120
- # TODO: this `== "lib"` check should be codified into a method somewhere
121
- if component_dir.path == "lib"
122
- # Expect component files in the root of the lib/ component dir to define
123
- # classes inside the slice's namespace.
124
- #
125
- # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
126
- # "foo"
127
- component_dir.namespaces.delete_root
128
- component_dir.namespaces.add_root(key: nil, const: namespace_path)
116
+ def export(keys)
117
+ container.config.exports = keys
118
+ end
129
119
 
130
- config.component_dirs.add(component_dir)
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?
131
123
 
132
- application.autoloader.push_dir(root.join("lib"), namespace: namespace)
133
- else
134
- # Expect component files in the root of non-lib/ component dirs to define
135
- # classes inside a namespace matching that dir.
136
- #
137
- # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
138
- # registered as "actions.foo"
124
+ application = self.application
139
125
 
140
- dir_namespace_path = File.join(namespace_path, component_dir.path)
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
141
131
 
142
- autoloader_namespace = begin
143
- inflector.constantize(inflector.camelize(dir_namespace_path))
144
- rescue NameError
145
- namespace.const_set(inflector.camelize(component_dir.path), Module.new)
146
- end
132
+ as = kwargs[:as] || slice_name
147
133
 
148
- component_dir.namespaces.delete_root
149
- component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path) # TODO: do we need to swap path delimiters for key delimiters here?
134
+ import(from: from, as: as, **kwargs)
135
+ end
136
+ end
150
137
 
151
- config.component_dirs.add(component_dir)
138
+ private
152
139
 
153
- application.autoloader.push_dir(
154
- container.root.join(component_dir.path),
155
- namespace: autoloader_namespace
156
- )
157
- end
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
145
+
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
154
+
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
165
+
166
+ def prepare_container_plugins
167
+ container.use(:env, inferrer: -> { Hanami.env })
168
+
169
+ container.use(
170
+ :zeitwerk,
171
+ loader: application.autoloader,
172
+ run_setup: false,
173
+ eager_load: false
174
+ )
175
+ end
176
+
177
+ def prepare_container_base_config
178
+ container.config.name = slice_name
179
+ container.config.root = root
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?
188
+
189
+ # Add component dirs for each configured component path
190
+ application.configuration.source_dirs.component_dirs.each do |component_dir|
191
+ next unless root.join(component_dir.path).directory?
192
+
193
+ component_dir = component_dir.dup
194
+
195
+ if component_dir.path == LIB_DIR
196
+ # Expect component files in the root of the lib/ component dir to define
197
+ # classes inside the slice's namespace.
198
+ #
199
+ # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
200
+ # "foo"
201
+ component_dir.namespaces.delete_root
202
+ component_dir.namespaces.add_root(key: nil, const: namespace_path)
203
+ else
204
+ # Expect component files in the root of non-lib/ component dirs to define
205
+ # classes inside a namespace matching that dir.
206
+ #
207
+ # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
208
+ # registered as "actions.foo"
209
+
210
+ dir_namespace_path = File.join(namespace_path, component_dir.path)
211
+
212
+ component_dir.namespaces.delete_root
213
+ component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
158
214
  end
159
215
 
160
- # Pass configured autoload dirs to the autoloader
161
- application.configuration.source_dirs.autoload_paths.each do |autoload_path|
162
- next unless root.join(autoload_path).directory?
216
+ container.config.component_dirs.add(component_dir)
217
+ end
218
+ end
163
219
 
164
- dir_namespace_path = File.join(namespace_path, autoload_path)
220
+ def prepare_autoloader # rubocop:disable Metrics/AbcSize
221
+ return unless root&.directory?
165
222
 
166
- autoloader_namespace = begin
167
- inflector.constantize(inflector.camelize(dir_namespace_path))
168
- rescue NameError
169
- namespace.const_set(inflector.camelize(autoload_path), Module.new)
170
- end
223
+ # Pass configured autoload dirs to the autoloader
224
+ application.configuration.source_dirs.autoload_paths.each do |autoload_path|
225
+ next unless root.join(autoload_path).directory?
171
226
 
172
- application.autoloader.push_dir(
173
- container.root.join(autoload_path),
174
- namespace: autoloader_namespace
175
- )
227
+ dir_namespace_path = File.join(namespace_path, autoload_path)
228
+
229
+ autoloader_namespace = begin
230
+ inflector.constantize(inflector.camelize(dir_namespace_path))
231
+ rescue NameError
232
+ namespace.const_set(inflector.camelize(autoload_path), Module.new)
176
233
  end
234
+
235
+ container.config.autoloader.push_dir(
236
+ container.root.join(autoload_path),
237
+ namespace: autoloader_namespace
238
+ )
177
239
  end
178
240
  end
179
241
 
180
- if namespace
242
+ def prepare_container_imports
243
+ container.import from: application.container, as: :application
244
+ end
245
+
246
+ def prepare_container_consts
181
247
  namespace.const_set :Container, container
182
248
  namespace.const_set :Deps, container.injector
183
249
  end
184
-
185
- container
186
250
  end
187
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
251
+ # rubocop:enable Metrics/ModuleLength
188
252
  end
189
253
  end
@@ -8,7 +8,7 @@ module Hanami
8
8
  module Version
9
9
  # @since 0.9.0
10
10
  # @api private
11
- VERSION = "2.0.0.alpha5"
11
+ VERSION = "2.0.0.alpha7.1"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
@@ -1,19 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
- require "rack/request"
5
- require "hanami/utils/hash"
6
-
7
3
  module Hanami
8
4
  module Web
9
5
  # Rack logger for Hanami applications
10
6
  class RackLogger
11
- attr_reader :logger
12
- attr_reader :filter_params
7
+ REQUEST_METHOD = "REQUEST_METHOD"
8
+ private_constant :REQUEST_METHOD
9
+
10
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
11
+ private_constant :HTTP_X_FORWARDED_FOR
12
+
13
+ REMOTE_ADDR = "REMOTE_ADDR"
14
+ private_constant :REMOTE_ADDR
15
+
16
+ SCRIPT_NAME = "SCRIPT_NAME"
17
+ private_constant :SCRIPT_NAME
18
+
19
+ PATH_INFO = "PATH_INFO"
20
+ private_constant :PATH_INFO
21
+
22
+ ROUTER_PARAMS = "router.params"
23
+ private_constant :ROUTER_PARAMS
24
+
25
+ CONTENT_LENGTH = "Content-Length"
26
+ private_constant :CONTENT_LENGTH
13
27
 
14
- def initialize(logger, filter_params: [])
28
+ def initialize(logger)
15
29
  @logger = logger
16
- @filter_params = filter_params
17
30
  end
18
31
 
19
32
  def attach(rack_monitor)
@@ -26,22 +39,20 @@ module Hanami
26
39
  end
27
40
  end
28
41
 
29
- # rubocop:disable Metrics/MethodLength
30
- def log_request(env, status, time)
42
+ def log_request(env, status, elapsed)
31
43
  data = {
32
- http: env[HTTP_VERSION],
33
44
  verb: env[REQUEST_METHOD],
34
45
  status: status,
46
+ elapsed: "#{elapsed}ms",
35
47
  ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
36
48
  path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
37
49
  length: extract_content_length(env),
38
- params: extract_params(env),
39
- elapsed: time,
50
+ params: env[ROUTER_PARAMS],
51
+ time: Time.now,
40
52
  }
41
53
 
42
- logger.info JSON.generate(data)
54
+ logger.info(data)
43
55
  end
44
- # rubocop:enable Metrics/MethodLength
45
56
 
46
57
  def log_exception(exception)
47
58
  logger.error exception.message
@@ -50,47 +61,12 @@ module Hanami
50
61
 
51
62
  private
52
63
 
53
- HTTP_VERSION = "HTTP_VERSION"
54
- REQUEST_METHOD = "REQUEST_METHOD"
55
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
56
- REMOTE_ADDR = "REMOTE_ADDR"
57
- SCRIPT_NAME = "SCRIPT_NAME"
58
- PATH_INFO = "PATH_INFO"
59
- RACK_ERRORS = "rack.errors"
60
- QUERY_HASH = "rack.request.query_hash"
61
- FORM_HASH = "rack.request.form_hash"
62
- ROUTER_PARAMS = "router.params"
63
- CONTENT_LENGTH = "Content-Length"
64
+ attr_reader :logger
64
65
 
65
66
  def extract_content_length(env)
66
67
  value = env[CONTENT_LENGTH]
67
68
  !value || value.to_s == "0" ? "-" : value
68
69
  end
69
-
70
- def extract_params(env)
71
- result = env.fetch(QUERY_HASH, {})
72
- result.merge!(env.fetch(FORM_HASH, {}))
73
- result.merge!(Hanami::Utils::Hash.deep_stringify(env.fetch(ROUTER_PARAMS, {})))
74
- result
75
- end
76
-
77
- FILTERED = "[FILTERED]"
78
-
79
- # rubocop:disable Metrics/MethodLength
80
- def filter(params)
81
- params.each_with_object({}) do |(k, v), h|
82
- if filter_params.include?(k)
83
- h.update(k => FILTERED)
84
- elsif v.is_a?(Hash)
85
- h.update(k => filter(v))
86
- elsif v.is_a?(Array)
87
- h.update(k => v.map { |m| m.is_a?(Hash) ? filter(m) : m })
88
- else
89
- h[k] = v
90
- end
91
- end
92
- end
93
- # rubocop:enable Metrics/MethodLength
94
70
  end
95
71
  end
96
72
  end
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
- raise "Hanami.application not configured" unless defined?(@_application)
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
- raise "Hanami.application already configured" if defined?(@_application)
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
@@ -47,8 +55,8 @@ module Hanami
47
55
  application[:logger]
48
56
  end
49
57
 
50
- def self.init
51
- application.init
58
+ def self.prepare
59
+ application.prepare
52
60
  end
53
61
 
54
62
  def self.boot
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.alpha5
4
+ version: 2.0.0.alpha7.1
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-01-12 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,20 +104,20 @@ dependencies:
104
104
  requirements:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
- version: '0.22'
107
+ version: '0.23'
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: 0.22.0
110
+ version: 0.23.0
111
111
  type: :runtime
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.22'
117
+ version: '0.23'
118
118
  - - ">="
119
119
  - !ruby/object:Gem::Version
120
- version: 0.22.0
120
+ version: 0.23.0
121
121
  - !ruby/object:Gem::Dependency
122
122
  name: hanami-cli
123
123
  requirement: !ruby/object:Gem::Requirement
@@ -217,13 +217,12 @@ files:
217
217
  - hanami.gemspec
218
218
  - lib/hanami.rb
219
219
  - lib/hanami/application.rb
220
- - lib/hanami/application/autoloader/inflector_adapter.rb
221
- - lib/hanami/application/container/boot/inflector.rb
222
- - lib/hanami/application/container/boot/logger.rb
223
- - lib/hanami/application/container/boot/rack_logger.rb
224
- - lib/hanami/application/container/boot/rack_monitor.rb
225
- - lib/hanami/application/container/boot/routes_helper.rb
226
- - lib/hanami/application/container/boot/settings.rb
220
+ - lib/hanami/application/container/providers/inflector.rb
221
+ - lib/hanami/application/container/providers/logger.rb
222
+ - lib/hanami/application/container/providers/rack_logger.rb
223
+ - lib/hanami/application/container/providers/rack_monitor.rb
224
+ - lib/hanami/application/container/providers/routes_helper.rb
225
+ - lib/hanami/application/container/providers/settings.rb
227
226
  - lib/hanami/application/router.rb
228
227
  - lib/hanami/application/routes.rb
229
228
  - lib/hanami/application/routes_helper.rb
@@ -234,6 +233,7 @@ files:
234
233
  - lib/hanami/application/routing/router.rb
235
234
  - lib/hanami/application/settings.rb
236
235
  - lib/hanami/application/settings/dotenv_store.rb
236
+ - lib/hanami/application/slice_registrar.rb
237
237
  - lib/hanami/assets/application_configuration.rb
238
238
  - lib/hanami/assets/configuration.rb
239
239
  - lib/hanami/boot.rb
@@ -247,13 +247,16 @@ files:
247
247
  - lib/hanami/cli/commands/command.rb
248
248
  - lib/hanami/cli/commands/server.rb
249
249
  - lib/hanami/configuration.rb
250
+ - lib/hanami/configuration/actions.rb
250
251
  - lib/hanami/configuration/logger.rb
251
252
  - lib/hanami/configuration/middleware.rb
252
253
  - lib/hanami/configuration/null_configuration.rb
253
254
  - lib/hanami/configuration/router.rb
254
255
  - lib/hanami/configuration/sessions.rb
255
256
  - lib/hanami/configuration/source_dirs.rb
256
- - lib/hanami/init.rb
257
+ - lib/hanami/constants.rb
258
+ - lib/hanami/errors.rb
259
+ - lib/hanami/prepare.rb
257
260
  - lib/hanami/server.rb
258
261
  - lib/hanami/setup.rb
259
262
  - lib/hanami/slice.rb
@@ -263,6 +266,7 @@ homepage: http://hanamirb.org
263
266
  licenses:
264
267
  - MIT
265
268
  metadata:
269
+ rubygems_mfa_required: 'true'
266
270
  allowed_push_host: https://rubygems.org
267
271
  post_install_message:
268
272
  rdoc_options: []
@@ -272,14 +276,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
272
276
  requirements:
273
277
  - - ">="
274
278
  - !ruby/object:Gem::Version
275
- version: 2.6.0
279
+ version: '3.0'
276
280
  required_rubygems_version: !ruby/object:Gem::Requirement
277
281
  requirements:
278
282
  - - ">"
279
283
  - !ruby/object:Gem::Version
280
284
  version: 1.3.1
281
285
  requirements: []
282
- rubygems_version: 3.2.3
286
+ rubygems_version: 3.3.3
283
287
  signing_key:
284
288
  specification_version: 4
285
289
  summary: The web, with simplicity