hanami 2.0.0.alpha7.1 → 2.0.0.alpha8
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 +15 -0
- data/lib/hanami/application/action/slice_configured_action.rb +103 -0
- data/lib/hanami/application/action.rb +72 -0
- data/lib/hanami/application/view/context.rb +95 -0
- data/lib/hanami/application/view/slice_configured_context.rb +71 -0
- data/lib/hanami/application/view/slice_configured_view.rb +101 -0
- data/lib/hanami/application/view.rb +24 -0
- data/lib/hanami/application/view_name_inferrer.rb +63 -0
- data/lib/hanami/application.rb +22 -48
- data/lib/hanami/configuration/actions/content_security_policy.rb +118 -0
- data/lib/hanami/configuration/actions/cookies.rb +29 -0
- data/lib/hanami/configuration/actions/sessions.rb +46 -0
- data/lib/hanami/configuration/actions.rb +16 -11
- data/lib/hanami/configuration/logger.rb +11 -8
- data/lib/hanami/configuration/views.rb +81 -0
- data/lib/hanami/configuration.rb +25 -40
- data/lib/hanami/constants.rb +6 -0
- data/lib/hanami/errors.rb +3 -0
- data/lib/hanami/slice.rb +11 -14
- data/lib/hanami/slice_configurable.rb +75 -0
- data/lib/hanami/slice_name.rb +111 -0
- data/lib/hanami/version.rb +1 -1
- metadata +16 -4
- data/lib/hanami/boot/source_dirs.rb +0 -44
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Configuration
|
|
5
|
+
class Actions
|
|
6
|
+
# Configuration for Content Security Policy in Hanami applications
|
|
7
|
+
#
|
|
8
|
+
# @since 2.0.0
|
|
9
|
+
class ContentSecurityPolicy
|
|
10
|
+
# @since 2.0.0
|
|
11
|
+
# @api private
|
|
12
|
+
def initialize(&blk)
|
|
13
|
+
@policy = {
|
|
14
|
+
base_uri: "'self'",
|
|
15
|
+
child_src: "'self'",
|
|
16
|
+
connect_src: "'self'",
|
|
17
|
+
default_src: "'none'",
|
|
18
|
+
font_src: "'self'",
|
|
19
|
+
form_action: "'self'",
|
|
20
|
+
frame_ancestors: "'self'",
|
|
21
|
+
frame_src: "'self'",
|
|
22
|
+
img_src: "'self' https: data:",
|
|
23
|
+
media_src: "'self'",
|
|
24
|
+
object_src: "'none'",
|
|
25
|
+
plugin_types: "application/pdf",
|
|
26
|
+
script_src: "'self'",
|
|
27
|
+
style_src: "'self' 'unsafe-inline' https:"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
blk&.(self)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @since 2.0.0
|
|
34
|
+
# @api private
|
|
35
|
+
def initialize_copy(original_object)
|
|
36
|
+
@policy = original_object.instance_variable_get(:@policy).dup
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get a CSP setting
|
|
41
|
+
#
|
|
42
|
+
# @param key [Symbol] the underscored name of the CPS setting
|
|
43
|
+
# @return [String,NilClass] the CSP setting, if any
|
|
44
|
+
#
|
|
45
|
+
# @since 2.0.0
|
|
46
|
+
# @api public
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# module MyApp
|
|
50
|
+
# class Application < Hanami::Application
|
|
51
|
+
# config.actions.content_security_policy[:base_uri] # => "'self'"
|
|
52
|
+
# end
|
|
53
|
+
# end
|
|
54
|
+
def [](key)
|
|
55
|
+
@policy[key]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Set a CSP setting
|
|
59
|
+
#
|
|
60
|
+
# @param key [Symbol] the underscored name of the CPS setting
|
|
61
|
+
# @param value [String] the CSP setting value
|
|
62
|
+
#
|
|
63
|
+
# @since 2.0.0
|
|
64
|
+
# @api public
|
|
65
|
+
#
|
|
66
|
+
# @example Replace a default value
|
|
67
|
+
# module MyApp
|
|
68
|
+
# class Application < Hanami::Application
|
|
69
|
+
# config.actions.content_security_policy[:plugin_types] = nil
|
|
70
|
+
# end
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# @example Append to a default value
|
|
74
|
+
# module MyApp
|
|
75
|
+
# class Application < Hanami::Application
|
|
76
|
+
# config.actions.content_security_policy[:script_src] += " https://my.cdn.test"
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
def []=(key, value)
|
|
80
|
+
@policy[key] = value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Deletes a CSP key
|
|
84
|
+
#
|
|
85
|
+
# @param key [Symbol] the underscored name of the CPS setting
|
|
86
|
+
#
|
|
87
|
+
# @since 2.0.0
|
|
88
|
+
# @api public
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# module MyApp
|
|
92
|
+
# class Application < Hanami::Application
|
|
93
|
+
# config.actions.content_security_policy.delete(:object_src)
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
def delete(key)
|
|
97
|
+
@policy.delete(key)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @since 2.0.0
|
|
101
|
+
# @api private
|
|
102
|
+
def to_str
|
|
103
|
+
@policy.map do |key, value|
|
|
104
|
+
"#{dasherize(key)} #{value}"
|
|
105
|
+
end.join(";\n")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# @since 2.0.0
|
|
111
|
+
# @api private
|
|
112
|
+
def dasherize(key)
|
|
113
|
+
key.to_s.gsub("_", "-")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
class Configuration
|
|
5
|
+
class Actions
|
|
6
|
+
# Wrapper for application-level configuration of HTTP cookies for Hanami actions.
|
|
7
|
+
# This decorates the hash of cookie options that is otherwise directly configurable
|
|
8
|
+
# on actions, and adds the `enabled?` method to allow `ApplicationAction` to
|
|
9
|
+
# determine whether to include the `Action::Cookies` module.
|
|
10
|
+
#
|
|
11
|
+
# @since 2.0.0
|
|
12
|
+
class Cookies
|
|
13
|
+
attr_reader :options
|
|
14
|
+
|
|
15
|
+
def initialize(options)
|
|
16
|
+
@options = options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def enabled?
|
|
20
|
+
!options.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
options.to_h
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/constants"
|
|
4
|
+
require "hanami/utils/string"
|
|
5
|
+
require "hanami/utils/class"
|
|
6
|
+
|
|
7
|
+
module Hanami
|
|
8
|
+
class Configuration
|
|
9
|
+
class Actions
|
|
10
|
+
# Configuration for HTTP sessions in Hanami actions
|
|
11
|
+
#
|
|
12
|
+
# @since 2.0.0
|
|
13
|
+
class Sessions
|
|
14
|
+
attr_reader :storage, :options
|
|
15
|
+
|
|
16
|
+
def initialize(storage = nil, *options)
|
|
17
|
+
@storage = storage
|
|
18
|
+
@options = options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def enabled?
|
|
22
|
+
!storage.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def middleware
|
|
26
|
+
return [] if !enabled?
|
|
27
|
+
|
|
28
|
+
[[storage_middleware, options]]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def storage_middleware
|
|
34
|
+
require_storage
|
|
35
|
+
|
|
36
|
+
name = Utils::String.classify(storage)
|
|
37
|
+
Utils::Class.load!(name, ::Rack::Session)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def require_storage
|
|
41
|
+
require "rack/session/#{storage}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
require "dry/configurable"
|
|
4
|
+
require "hanami/action/configuration"
|
|
5
|
+
require_relative "actions/cookies"
|
|
6
|
+
require_relative "actions/sessions"
|
|
7
|
+
require_relative "actions/content_security_policy"
|
|
8
|
+
require_relative "../application/view_name_inferrer"
|
|
8
9
|
|
|
9
10
|
module Hanami
|
|
10
11
|
class Configuration
|
|
12
|
+
# Hanami actions configuration
|
|
13
|
+
#
|
|
14
|
+
# @since 2.0.0
|
|
11
15
|
class Actions
|
|
12
16
|
include Dry::Configurable
|
|
13
17
|
|
|
@@ -17,15 +21,18 @@ module Hanami
|
|
|
17
21
|
|
|
18
22
|
setting :name_inference_base, default: "actions"
|
|
19
23
|
setting :view_context_identifier, default: "view.context"
|
|
20
|
-
setting :view_name_inferrer, default: ViewNameInferrer
|
|
24
|
+
setting :view_name_inferrer, default: Application::ViewNameInferrer
|
|
21
25
|
setting :view_name_inference_base, default: "views"
|
|
22
26
|
|
|
23
27
|
attr_accessor :content_security_policy
|
|
24
28
|
|
|
29
|
+
attr_reader :base_configuration
|
|
30
|
+
private :base_configuration
|
|
31
|
+
|
|
25
32
|
def initialize(*, **options)
|
|
26
33
|
super()
|
|
27
34
|
|
|
28
|
-
@base_configuration = Configuration.new
|
|
35
|
+
@base_configuration = Hanami::Action::Configuration.new
|
|
29
36
|
@content_security_policy = ContentSecurityPolicy.new do |csp|
|
|
30
37
|
if assets_server_url = options[:assets_server_url]
|
|
31
38
|
csp[:script_src] += " #{assets_server_url}"
|
|
@@ -41,8 +48,8 @@ module Hanami
|
|
|
41
48
|
# (neither true nor false), so we can default it to whether sessions are enabled
|
|
42
49
|
self.csrf_protection = sessions.enabled? if csrf_protection.nil?
|
|
43
50
|
|
|
44
|
-
if
|
|
45
|
-
|
|
51
|
+
if content_security_policy
|
|
52
|
+
default_headers["Content-Security-Policy"] = content_security_policy.to_str
|
|
46
53
|
end
|
|
47
54
|
end
|
|
48
55
|
|
|
@@ -58,8 +65,6 @@ module Hanami
|
|
|
58
65
|
|
|
59
66
|
private
|
|
60
67
|
|
|
61
|
-
attr_reader :base_configuration
|
|
62
|
-
|
|
63
68
|
# Apply defaults for base configuration settings
|
|
64
69
|
def configure_defaults
|
|
65
70
|
self.default_request_format = :html
|
|
@@ -11,9 +11,9 @@ module Hanami
|
|
|
11
11
|
class Logger
|
|
12
12
|
include Dry::Configurable
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
attr_reader :application_name
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
protected :config
|
|
17
17
|
|
|
18
18
|
setting :level
|
|
19
19
|
|
|
@@ -30,7 +30,6 @@ module Hanami
|
|
|
30
30
|
setting :logger_class, default: Hanami::Logger
|
|
31
31
|
|
|
32
32
|
def initialize(env:, application_name:)
|
|
33
|
-
@env = env
|
|
34
33
|
@application_name = application_name
|
|
35
34
|
|
|
36
35
|
config.level = case env
|
|
@@ -58,12 +57,16 @@ module Hanami
|
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
def finalize!
|
|
62
|
-
config.application_name = @application_name.call
|
|
63
|
-
end
|
|
64
|
-
|
|
65
60
|
def instance
|
|
66
|
-
logger_class.new(
|
|
61
|
+
logger_class.new(
|
|
62
|
+
application_name.name,
|
|
63
|
+
*options,
|
|
64
|
+
stream: stream,
|
|
65
|
+
level: level,
|
|
66
|
+
formatter: formatter,
|
|
67
|
+
filter: filters,
|
|
68
|
+
colorizer: colors
|
|
69
|
+
)
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
private
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/configurable"
|
|
4
|
+
require "hanami/view"
|
|
5
|
+
|
|
6
|
+
module Hanami
|
|
7
|
+
class Configuration
|
|
8
|
+
# Hanami actions configuration
|
|
9
|
+
#
|
|
10
|
+
# @since 2.0.0
|
|
11
|
+
class Views
|
|
12
|
+
include Dry::Configurable
|
|
13
|
+
|
|
14
|
+
setting :parts_path, default: "view/parts"
|
|
15
|
+
|
|
16
|
+
attr_reader :base_configuration
|
|
17
|
+
private :base_configuration
|
|
18
|
+
|
|
19
|
+
def initialize(*)
|
|
20
|
+
super
|
|
21
|
+
|
|
22
|
+
@base_configuration = Hanami::View.config.dup
|
|
23
|
+
|
|
24
|
+
configure_defaults
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the list of available settings
|
|
28
|
+
#
|
|
29
|
+
# @return [Set]
|
|
30
|
+
#
|
|
31
|
+
# @since 2.0.0
|
|
32
|
+
# @api private
|
|
33
|
+
def settings
|
|
34
|
+
self.class.settings + View.settings - NON_FORWARDABLE_METHODS
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def finalize!
|
|
38
|
+
return self if frozen?
|
|
39
|
+
|
|
40
|
+
base_configuration.finalize!
|
|
41
|
+
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def configure_defaults
|
|
48
|
+
self.paths = ["templates"]
|
|
49
|
+
self.template_inference_base = "views"
|
|
50
|
+
self.layout = "application"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# An inflector for views is not configurable via `config.views.inflector` on an
|
|
54
|
+
# `Hanami::Application`. The application-wide inflector is already configurable
|
|
55
|
+
# there as `config.inflector` and will be used as the default inflector for views.
|
|
56
|
+
#
|
|
57
|
+
# A custom inflector may still be provided in an `Hanami::View` subclass, via
|
|
58
|
+
# `config.inflector=`.
|
|
59
|
+
NON_FORWARDABLE_METHODS = %i[inflector inflector=].freeze
|
|
60
|
+
private_constant :NON_FORWARDABLE_METHODS
|
|
61
|
+
|
|
62
|
+
def method_missing(name, *args, &block)
|
|
63
|
+
return super if NON_FORWARDABLE_METHODS.include?(name)
|
|
64
|
+
|
|
65
|
+
if config.respond_to?(name)
|
|
66
|
+
config.public_send(name, *args, &block)
|
|
67
|
+
elsif base_configuration.respond_to?(name)
|
|
68
|
+
base_configuration.public_send(name, *args, &block)
|
|
69
|
+
else
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def respond_to_missing?(name, _include_all = false)
|
|
75
|
+
return false if NON_FORWARDABLE_METHODS.include?(name)
|
|
76
|
+
|
|
77
|
+
config.respond_to?(name) || base_configuration.respond_to?(name) || super
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/hanami/configuration.rb
CHANGED
|
@@ -19,14 +19,13 @@ module Hanami
|
|
|
19
19
|
# Hanami application configuration
|
|
20
20
|
#
|
|
21
21
|
# @since 2.0.0
|
|
22
|
-
#
|
|
23
|
-
# rubocop:disable Metrics/ClassLength
|
|
24
22
|
class Configuration
|
|
25
23
|
include Dry::Configurable
|
|
26
24
|
|
|
27
25
|
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
|
28
26
|
private_constant :DEFAULT_ENVIRONMENTS
|
|
29
27
|
|
|
28
|
+
attr_reader :application_name
|
|
30
29
|
attr_reader :env
|
|
31
30
|
|
|
32
31
|
attr_reader :actions
|
|
@@ -37,8 +36,9 @@ module Hanami
|
|
|
37
36
|
attr_reader :environments
|
|
38
37
|
private :environments
|
|
39
38
|
|
|
39
|
+
# rubocop:disable Metrics/AbcSize
|
|
40
40
|
def initialize(application_name:, env:)
|
|
41
|
-
@
|
|
41
|
+
@application_name = application_name
|
|
42
42
|
|
|
43
43
|
@environments = DEFAULT_ENVIRONMENTS.clone
|
|
44
44
|
@env = env
|
|
@@ -48,46 +48,29 @@ module Hanami
|
|
|
48
48
|
self.root = Dir.pwd
|
|
49
49
|
self.settings_store = Application::Settings::DotenvStore.new.with_dotenv_loaded
|
|
50
50
|
|
|
51
|
-
config.logger = Configuration::Logger.new(env: env, application_name:
|
|
51
|
+
config.logger = Configuration::Logger.new(env: env, application_name: application_name)
|
|
52
52
|
|
|
53
|
-
@assets =
|
|
54
|
-
require_path = "hanami/assets/application_configuration"
|
|
55
|
-
require require_path
|
|
53
|
+
@assets = load_dependent_config("hanami/assets/application_configuration") {
|
|
56
54
|
Hanami::Assets::ApplicationConfiguration.new
|
|
57
|
-
|
|
58
|
-
raise e unless e.path == require_path
|
|
59
|
-
require_relative "configuration/null_configuration"
|
|
60
|
-
NullConfiguration.new
|
|
61
|
-
end
|
|
55
|
+
}
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
require require_path
|
|
68
|
-
Hanami::Action::ApplicationConfiguration.new(assets_server_url: assets.server_url)
|
|
69
|
-
rescue LoadError => e
|
|
70
|
-
raise e unless e.path == require_path
|
|
71
|
-
require_relative "configuration/null_configuration"
|
|
72
|
-
NullConfiguration.new
|
|
73
|
-
end
|
|
57
|
+
@actions = load_dependent_config("hanami/action") {
|
|
58
|
+
require_relative "configuration/actions"
|
|
59
|
+
Actions.new
|
|
60
|
+
}
|
|
74
61
|
|
|
75
62
|
@middleware = Middleware.new
|
|
76
63
|
|
|
77
64
|
@router = Router.new(self)
|
|
78
65
|
|
|
79
|
-
@views =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rescue LoadError => e
|
|
84
|
-
raise e unless e.path == require_path
|
|
85
|
-
require_relative "configuration/null_configuration"
|
|
86
|
-
NullConfiguration.new
|
|
87
|
-
end
|
|
66
|
+
@views = load_dependent_config("hanami/view") {
|
|
67
|
+
require_relative "configuration/views"
|
|
68
|
+
Views.new
|
|
69
|
+
}
|
|
88
70
|
|
|
89
71
|
yield self if block_given?
|
|
90
72
|
end
|
|
73
|
+
# rubocop:enable Metrics/AbcSize
|
|
91
74
|
|
|
92
75
|
def environment(env_name, &block)
|
|
93
76
|
environments[env_name] << block
|
|
@@ -109,14 +92,6 @@ module Hanami
|
|
|
109
92
|
super
|
|
110
93
|
end
|
|
111
94
|
|
|
112
|
-
def namespace
|
|
113
|
-
inflector.constantize(@namespace)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def application_name
|
|
117
|
-
inflector.underscore(@namespace).to_sym
|
|
118
|
-
end
|
|
119
|
-
|
|
120
95
|
setting :root, constructor: -> path { Pathname(path) }
|
|
121
96
|
|
|
122
97
|
setting :inflector, default: Dry::Inflector.new
|
|
@@ -162,6 +137,16 @@ module Hanami
|
|
|
162
137
|
end
|
|
163
138
|
end
|
|
164
139
|
|
|
140
|
+
def load_dependent_config(require_path, &block)
|
|
141
|
+
require require_path
|
|
142
|
+
yield
|
|
143
|
+
rescue LoadError => e
|
|
144
|
+
raise e unless e.path == require_path
|
|
145
|
+
|
|
146
|
+
require_relative "configuration/null_configuration"
|
|
147
|
+
NullConfiguration.new
|
|
148
|
+
end
|
|
149
|
+
|
|
165
150
|
def method_missing(name, *args, &block)
|
|
166
151
|
if config.respond_to?(name)
|
|
167
152
|
config.public_send(name, *args, &block)
|
data/lib/hanami/constants.rb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Hanami
|
|
4
|
+
CONTAINER_KEY_DELIMITER = "."
|
|
5
|
+
private_constant :CONTAINER_KEY_DELIMITER
|
|
6
|
+
|
|
4
7
|
# @api private
|
|
5
8
|
MODULE_DELIMITER = "::"
|
|
6
9
|
private_constant :MODULE_DELIMITER
|
|
7
10
|
|
|
11
|
+
PATH_DELIMITER = "/"
|
|
12
|
+
private_constant :PATH_DELIMITER
|
|
13
|
+
|
|
8
14
|
# @api private
|
|
9
15
|
CONFIG_DIR = "config"
|
|
10
16
|
private_constant :CONFIG_DIR
|
data/lib/hanami/errors.rb
CHANGED
data/lib/hanami/slice.rb
CHANGED
|
@@ -4,20 +4,21 @@ require "dry/system/container"
|
|
|
4
4
|
require "hanami/errors"
|
|
5
5
|
require "pathname"
|
|
6
6
|
require_relative "constants"
|
|
7
|
+
require_relative "slice_name"
|
|
7
8
|
|
|
8
9
|
module Hanami
|
|
9
10
|
# Distinct area of concern within an Hanami application
|
|
10
11
|
#
|
|
11
12
|
# @since 2.0.0
|
|
12
13
|
class Slice
|
|
13
|
-
def self.inherited(
|
|
14
|
+
def self.inherited(subclass)
|
|
14
15
|
super
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
subclass.extend(ClassMethods)
|
|
17
18
|
|
|
18
19
|
# Eagerly initialize any variables that may be accessed inside the subclass body
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
subclass.instance_variable_set(:@application, Hanami.application)
|
|
21
|
+
subclass.instance_variable_set(:@container, Class.new(Dry::System::Container))
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
# rubocop:disable Metrics/ModuleLength
|
|
@@ -25,15 +26,11 @@ module Hanami
|
|
|
25
26
|
attr_reader :application, :container
|
|
26
27
|
|
|
27
28
|
def slice_name
|
|
28
|
-
|
|
29
|
+
@slice_name ||= SliceName.new(self, inflector: method(:inflector))
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def namespace
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def namespace_path
|
|
36
|
-
inflector.underscore(namespace)
|
|
33
|
+
slice_name.namespace
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
def root
|
|
@@ -175,7 +172,7 @@ module Hanami
|
|
|
175
172
|
end
|
|
176
173
|
|
|
177
174
|
def prepare_container_base_config
|
|
178
|
-
container.config.name = slice_name
|
|
175
|
+
container.config.name = slice_name.to_sym
|
|
179
176
|
container.config.root = root
|
|
180
177
|
container.config.provider_dirs = [File.join("config", "providers")]
|
|
181
178
|
|
|
@@ -199,7 +196,7 @@ module Hanami
|
|
|
199
196
|
# e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
|
|
200
197
|
# "foo"
|
|
201
198
|
component_dir.namespaces.delete_root
|
|
202
|
-
component_dir.namespaces.add_root(key: nil, const:
|
|
199
|
+
component_dir.namespaces.add_root(key: nil, const: slice_name.name)
|
|
203
200
|
else
|
|
204
201
|
# Expect component files in the root of non-lib/ component dirs to define
|
|
205
202
|
# classes inside a namespace matching that dir.
|
|
@@ -207,7 +204,7 @@ module Hanami
|
|
|
207
204
|
# e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
|
|
208
205
|
# registered as "actions.foo"
|
|
209
206
|
|
|
210
|
-
dir_namespace_path = File.join(
|
|
207
|
+
dir_namespace_path = File.join(slice_name.name, component_dir.path)
|
|
211
208
|
|
|
212
209
|
component_dir.namespaces.delete_root
|
|
213
210
|
component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
|
|
@@ -224,7 +221,7 @@ module Hanami
|
|
|
224
221
|
application.configuration.source_dirs.autoload_paths.each do |autoload_path|
|
|
225
222
|
next unless root.join(autoload_path).directory?
|
|
226
223
|
|
|
227
|
-
dir_namespace_path = File.join(
|
|
224
|
+
dir_namespace_path = File.join(slice_name.name, autoload_path)
|
|
228
225
|
|
|
229
226
|
autoloader_namespace = begin
|
|
230
227
|
inflector.constantize(inflector.camelize(dir_namespace_path))
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
# Calls `configure_for_slice(slice)` on the extended class whenever it is first
|
|
7
|
+
# subclassed within a module namespace corresponding to a slice.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# class BaseClass
|
|
11
|
+
# extend Hanami::SliceConfigurable
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# # slices/main/lib/my_class.rb
|
|
15
|
+
# module Main
|
|
16
|
+
# class MyClass < BaseClass
|
|
17
|
+
# # Will be called with `Main::Slice`
|
|
18
|
+
# def self.configure_for_slice(slice)
|
|
19
|
+
# # ...
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
25
|
+
# @since 2.0.0
|
|
26
|
+
module SliceConfigurable
|
|
27
|
+
class << self
|
|
28
|
+
def extended(klass)
|
|
29
|
+
slice_for = method(:slice_for)
|
|
30
|
+
|
|
31
|
+
inherited_mod = Module.new do
|
|
32
|
+
define_method(:inherited) do |subclass|
|
|
33
|
+
unless Hanami.application?
|
|
34
|
+
raise ComponentLoadError, "Class #{klass} must be defined within an Hanami application"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
super(subclass)
|
|
38
|
+
|
|
39
|
+
subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)
|
|
40
|
+
|
|
41
|
+
slice = slice_for.(subclass)
|
|
42
|
+
return unless slice
|
|
43
|
+
|
|
44
|
+
unless subclass.configured_for_slice?(slice)
|
|
45
|
+
subclass.configure_for_slice(slice)
|
|
46
|
+
subclass.configured_for_slices << slice
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
klass.singleton_class.prepend(inherited_mod)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def slice_for(klass)
|
|
57
|
+
return unless klass.name
|
|
58
|
+
|
|
59
|
+
slices = Hanami.application.slices.to_a + [Hanami.application]
|
|
60
|
+
|
|
61
|
+
slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def configure_for_slice(slice); end
|
|
66
|
+
|
|
67
|
+
def configured_for_slice?(slice)
|
|
68
|
+
configured_for_slices.include?(slice)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def configured_for_slices
|
|
72
|
+
@configured_for_slices ||= []
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|