piko-lite-box 0.0.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.
- checksums.yaml +7 -0
- data/hanami-3.0.1/CHANGELOG.md +1701 -0
- data/hanami-3.0.1/LICENSE +20 -0
- data/hanami-3.0.1/README.md +89 -0
- data/hanami-3.0.1/hanami.gemspec +49 -0
- data/hanami-3.0.1/lib/hanami/app.rb +197 -0
- data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
- data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
- data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
- data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
- data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
- data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
- data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
- data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
- data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
- data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
- data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
- data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
- data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
- data/hanami-3.0.1/lib/hanami/config.rb +564 -0
- data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
- data/hanami-3.0.1/lib/hanami/env.rb +52 -0
- data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
- data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
- data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
- data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
- data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
- data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
- data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
- data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
- data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
- data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
- data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
- data/hanami-3.0.1/lib/hanami/port.rb +45 -0
- data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
- data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
- data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
- data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
- data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
- data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
- data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
- data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
- data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
- data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
- data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
- data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
- data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
- data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
- data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
- data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
- data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
- data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
- data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
- data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
- data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
- data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
- data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
- data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
- data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
- data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
- data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
- data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
- data/hanami-3.0.1/lib/hanami/version.rb +45 -0
- data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
- data/hanami-3.0.1/lib/hanami.rb +276 -0
- data/piko-lite-box.gemspec +11 -0
- metadata +137 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Hanami
|
|
7
|
+
# Calls `configure_for_slice(slice)` on the extended class whenever it is first
|
|
8
|
+
# subclassed within a module namespace corresponding to a slice.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# class BaseClass
|
|
12
|
+
# extend Hanami::SliceConfigurable
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# # slices/main/lib/my_class.rb
|
|
16
|
+
# module Main
|
|
17
|
+
# class MyClass < BaseClass
|
|
18
|
+
# # Will be called with `Main::Slice`
|
|
19
|
+
# def self.configure_for_slice(slice)
|
|
20
|
+
# # ...
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
# @since 2.0.0
|
|
27
|
+
module SliceConfigurable
|
|
28
|
+
class << self
|
|
29
|
+
def extended(klass)
|
|
30
|
+
slice_for = method(:slice_for)
|
|
31
|
+
|
|
32
|
+
inherited_mod = Module.new do
|
|
33
|
+
define_method(:inherited) do |subclass|
|
|
34
|
+
unless Hanami.app?
|
|
35
|
+
raise ComponentLoadError, "Class #{klass} must be defined within an Hanami app"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
super(subclass)
|
|
39
|
+
|
|
40
|
+
subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)
|
|
41
|
+
|
|
42
|
+
slice = slice_for.(subclass)
|
|
43
|
+
return unless slice
|
|
44
|
+
|
|
45
|
+
unless subclass.configured_for_slice?(slice)
|
|
46
|
+
subclass.configure_for_slice(slice) if subclass.respond_to?(:configure_for_slice)
|
|
47
|
+
subclass.configured_for_slices << slice
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
klass.singleton_class.prepend(inherited_mod)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def slice_for(klass)
|
|
58
|
+
return unless klass.name
|
|
59
|
+
|
|
60
|
+
Hanami.app.with_slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def configured_for_slice?(slice)
|
|
65
|
+
configured_for_slices.include?(slice)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def configured_for_slices
|
|
69
|
+
@configured_for_slices ||= []
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
# Represents the name of an {App} or {Slice}.
|
|
7
|
+
#
|
|
8
|
+
# @see Slice::ClassMethods#slice_name
|
|
9
|
+
# @see App::ClassMethods#app_name
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
# @since 2.0.0
|
|
13
|
+
class SliceName
|
|
14
|
+
# Returns a new SliceName for the slice or app.
|
|
15
|
+
#
|
|
16
|
+
# You must provide an inflector for the manipulation of the name into various formats.
|
|
17
|
+
# This should be given in the form of a Proc that returns the inflector when called.
|
|
18
|
+
# The reason for this is that the inflector may be replaced by the user during the
|
|
19
|
+
# app configuration phase, so the proc should ensure that the current instance
|
|
20
|
+
# of the inflector is returned whenever needed.
|
|
21
|
+
#
|
|
22
|
+
# @param slice [#name] the slice or app object
|
|
23
|
+
# @param inflector [Proc] Proc returning the app's inflector when called
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
def initialize(slice, inflector:)
|
|
27
|
+
@slice = slice
|
|
28
|
+
@inflector = inflector
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the name of the slice as a downcased, underscored string.
|
|
32
|
+
#
|
|
33
|
+
# This is considered the canonical name of the slice.
|
|
34
|
+
#
|
|
35
|
+
# @example
|
|
36
|
+
# slice_name.name # => "main"
|
|
37
|
+
#
|
|
38
|
+
# @return [String] the slice name
|
|
39
|
+
#
|
|
40
|
+
# @api public
|
|
41
|
+
# @since 2.0.0
|
|
42
|
+
def name
|
|
43
|
+
inflector.underscore(namespace_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @api public
|
|
47
|
+
# @since 2.0.0
|
|
48
|
+
alias_method :path, :name
|
|
49
|
+
|
|
50
|
+
# Returns the name of the slice's module namespace.
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# slice_name.namespace_name # => "Main"
|
|
54
|
+
#
|
|
55
|
+
# @return [String] the namespace name
|
|
56
|
+
#
|
|
57
|
+
# @api public
|
|
58
|
+
# @since 2.0.0
|
|
59
|
+
def namespace_name
|
|
60
|
+
slice_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the constant for the slice's module namespace.
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
# slice_name.namespace_const # => Main
|
|
67
|
+
#
|
|
68
|
+
# @return [Module] the namespace module constant
|
|
69
|
+
#
|
|
70
|
+
# @api public
|
|
71
|
+
# @since 2.0.0
|
|
72
|
+
def namespace_const
|
|
73
|
+
inflector.constantize(namespace_name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api public
|
|
77
|
+
# @since 2.0.0
|
|
78
|
+
alias_method :namespace, :namespace_const
|
|
79
|
+
|
|
80
|
+
# @api public
|
|
81
|
+
# @since 2.0.0
|
|
82
|
+
alias_method :to_s, :name
|
|
83
|
+
|
|
84
|
+
# Returns the name of a slice as a downcased, underscored symbol.
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# slice_name.name # => :main
|
|
88
|
+
#
|
|
89
|
+
# @return [Symbol] the slice name
|
|
90
|
+
#
|
|
91
|
+
# @see name, to_s
|
|
92
|
+
#
|
|
93
|
+
# @api public
|
|
94
|
+
# @since 2.0.0
|
|
95
|
+
def to_sym
|
|
96
|
+
name.to_sym
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def slice_name
|
|
102
|
+
@slice.name
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The inflector is callable to allow for it to be configured/replaced after this
|
|
106
|
+
# object has been initialized
|
|
107
|
+
def inflector
|
|
108
|
+
@inflector.()
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
# @api private
|
|
7
|
+
class SliceRegistrar
|
|
8
|
+
VALID_SLICE_NAME_RE = /^[a-z][a-z0-9_]*$/
|
|
9
|
+
SLICE_DELIMITER = CONTAINER_KEY_DELIMITER
|
|
10
|
+
|
|
11
|
+
attr_reader :parent, :slices
|
|
12
|
+
private :parent, :slices
|
|
13
|
+
|
|
14
|
+
def initialize(parent)
|
|
15
|
+
@parent = parent
|
|
16
|
+
@slices = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def register(name, slice_class = nil, &block)
|
|
20
|
+
unless name.to_s =~ VALID_SLICE_NAME_RE
|
|
21
|
+
raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return unless filter_slice_names([name]).any?
|
|
25
|
+
|
|
26
|
+
if slices.key?(name.to_sym)
|
|
27
|
+
raise SliceLoadError, "Slice '#{name}' is already registered"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
slice = slice_class || build_slice(name, &block)
|
|
31
|
+
|
|
32
|
+
configure_slice(name, slice)
|
|
33
|
+
|
|
34
|
+
slices[name.to_sym] = slice
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def [](name)
|
|
38
|
+
slices.fetch(name) do
|
|
39
|
+
raise SliceLoadError, "Slice '#{name}' not found"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def freeze
|
|
44
|
+
slices.freeze
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def load_slices
|
|
49
|
+
slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
|
|
50
|
+
.map { _1.basename(RB_EXT) }
|
|
51
|
+
|
|
52
|
+
slice_dirs = root.join(SLICES_DIR).glob("*")
|
|
53
|
+
.select(&:directory?)
|
|
54
|
+
.map { _1.basename }
|
|
55
|
+
|
|
56
|
+
(slice_dirs + slice_configs).uniq.sort
|
|
57
|
+
.then { filter_slice_names(_1) }
|
|
58
|
+
.each(&method(:load_slice))
|
|
59
|
+
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def each(&block)
|
|
64
|
+
slices.each_value(&block)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def keys
|
|
68
|
+
slices.keys
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_a
|
|
72
|
+
slices.values
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def with_nested
|
|
76
|
+
to_a.flat_map { |slice|
|
|
77
|
+
# Return nested slices first so that their more specific namespaces may be picked up first
|
|
78
|
+
# by SliceConfigurable#slice_for
|
|
79
|
+
slice.slices.with_nested + [slice]
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def root
|
|
86
|
+
parent.root
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def inflector
|
|
90
|
+
parent.inflector
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def parent_slice_namespace
|
|
94
|
+
parent.eql?(parent.app) ? Object : parent.namespace
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Runs when a slice file has been found inside the app at `config/slices/[slice_name].rb`,
|
|
98
|
+
# or when a slice directory exists at `slices/[slice_name]`.
|
|
99
|
+
#
|
|
100
|
+
# If a slice definition file is found by `find_slice_require_path`, then `load_slice` will
|
|
101
|
+
# require the file before registering the slice class.
|
|
102
|
+
#
|
|
103
|
+
# If a slice class is not found, registering the slice will generate the slice class.
|
|
104
|
+
def load_slice(slice_name)
|
|
105
|
+
slice_require_path = find_slice_require_path(slice_name)
|
|
106
|
+
require slice_require_path if slice_require_path
|
|
107
|
+
|
|
108
|
+
slice_class =
|
|
109
|
+
begin
|
|
110
|
+
inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
|
|
111
|
+
rescue NameError => exception
|
|
112
|
+
raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
register(slice_name, slice_class)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Finds the path to the slice's definition file, if it exists, in the following order:
|
|
119
|
+
#
|
|
120
|
+
# 1. `config/slices/[slice_name].rb`
|
|
121
|
+
# 2. `slices/[parent_slice_name]/config/[slice_name].rb` (unless parent is the app)
|
|
122
|
+
# 3. `slices/[slice_name]/config/slice.rb`
|
|
123
|
+
#
|
|
124
|
+
# If the slice is nested under another slice then it will look in the following order:
|
|
125
|
+
#
|
|
126
|
+
# 1. `config/slices/[parent_slice_name]/[slice_name].rb`
|
|
127
|
+
# 2. `slices/[parent_slice_name]/config/[slice_name].rb`
|
|
128
|
+
# 3. `slices/[parent_slice_name]/[slice_name]/config/slice.rb`
|
|
129
|
+
def find_slice_require_path(slice_name)
|
|
130
|
+
app_slice_file_path = [slice_name]
|
|
131
|
+
app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
|
|
132
|
+
ancestors = [
|
|
133
|
+
parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
|
|
134
|
+
parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
|
|
135
|
+
root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
ancestors
|
|
139
|
+
.uniq
|
|
140
|
+
.find { _1.sub_ext(RB_EXT).file? }
|
|
141
|
+
&.to_s
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def build_slice(slice_name, &block)
|
|
145
|
+
slice_module =
|
|
146
|
+
begin
|
|
147
|
+
inflector.constantize(slice_module_name(slice_name))
|
|
148
|
+
rescue NameError
|
|
149
|
+
parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def slice_module_name(slice_name)
|
|
156
|
+
inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def configure_slice(slice_name, slice)
|
|
160
|
+
slice.instance_variable_set(:@parent, parent)
|
|
161
|
+
|
|
162
|
+
# Slices require a root, so provide a sensible default based on the slice's parent
|
|
163
|
+
slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s)
|
|
164
|
+
|
|
165
|
+
slice.config.slices = child_slice_names(slice_name, parent.config.slices)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns a filtered array of slice names based on the parent's `config.slices`
|
|
169
|
+
#
|
|
170
|
+
# This works with both singular slice names (e.g. `"admin"`) as well as dot-delimited nested
|
|
171
|
+
# slice names (e.g. `"admin.shop"`).
|
|
172
|
+
#
|
|
173
|
+
# It will consider only the base names of the slices (since in this case, a parent slice must be
|
|
174
|
+
# loaded in order for its children to be loaded).
|
|
175
|
+
#
|
|
176
|
+
# @example
|
|
177
|
+
# parent.config.slices # => ["admin.shop"]
|
|
178
|
+
# filter_slice_names(["admin", "main"]) # => ["admin"]
|
|
179
|
+
#
|
|
180
|
+
# parent.config.slices # => ["admin"]
|
|
181
|
+
# filter_slice_names(["admin", "main"]) # => ["admin"]
|
|
182
|
+
def filter_slice_names(slice_names)
|
|
183
|
+
slice_names = slice_names.map(&:to_s)
|
|
184
|
+
|
|
185
|
+
if parent.config.slices
|
|
186
|
+
slice_names & parent.config.slices.map { base_slice_name(_1) }
|
|
187
|
+
else
|
|
188
|
+
slice_names
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns the base slice name from an (optionally) dot-delimited nested slice name.
|
|
193
|
+
#
|
|
194
|
+
# @example
|
|
195
|
+
# base_slice_name("admin") # => "admin"
|
|
196
|
+
# base_slice_name("admin.users") # => "admin"
|
|
197
|
+
def base_slice_name(name)
|
|
198
|
+
name.to_s.split(SLICE_DELIMITER).first
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Returns an array of slice names specific to the given child slice.
|
|
202
|
+
#
|
|
203
|
+
# @example
|
|
204
|
+
# child_local_slice_names("admin", ["main", "admin.users"]) # => ["users"]
|
|
205
|
+
def child_slice_names(parent_slice_name, slice_names)
|
|
206
|
+
slice_names
|
|
207
|
+
&.select { |name|
|
|
208
|
+
name.include?(SLICE_DELIMITER) && name.split(SLICE_DELIMITER)[0] == parent_slice_name.to_s
|
|
209
|
+
}
|
|
210
|
+
&.map { |name|
|
|
211
|
+
name.split(SLICE_DELIMITER)[1..].join(SLICE_DELIMITER) # which version of Ruby supports this?
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
# An adapter that optionally wraps the logger configured for a Hanami app. Ensures that both
|
|
7
|
+
# structured and tagged logging can be used across the Hanami framework.
|
|
8
|
+
#
|
|
9
|
+
# Provides `.call` as its main entrypoint, expecting a logger object. If a compatible logger is
|
|
10
|
+
# given (such as the Dry Logger instance provided by default in Hanami apps), then that logger is
|
|
11
|
+
# returned directly and not wrapped.
|
|
12
|
+
#
|
|
13
|
+
# If a non-compatible logger is given, then it will be wrapped by an instance of UniversalLogger,
|
|
14
|
+
# which adapts a structured and tagged logging API to the given logger.
|
|
15
|
+
#
|
|
16
|
+
# This leads to two levels of logger enhancement:
|
|
17
|
+
#
|
|
18
|
+
# 1. Structured-capable loggers (accepts keyword arguments, but no `#tagged` method): tags are are
|
|
19
|
+
# provided as a `:tags` keyword argument when logging.
|
|
20
|
+
# 2. Legacy loggers (such as the Ruby standard `Logger`, no keyword arguments, no `#tagged`
|
|
21
|
+
# method): messages are logged as JSON, with tags under a `"tags"` key.
|
|
22
|
+
#
|
|
23
|
+
# This adapter is used for all loggers configured in Hanami apps.
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @since 3.0.0
|
|
27
|
+
class UniversalLogger
|
|
28
|
+
class << self
|
|
29
|
+
# Wrap a logger if needed, or return it as-is if fully compatible.
|
|
30
|
+
#
|
|
31
|
+
# @param logger [Object] the logger to wrap
|
|
32
|
+
# @return [Object, UniversalLogger] the original logger or wrapped logger
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
def call(logger)
|
|
36
|
+
return logger if compatible_logger?(logger)
|
|
37
|
+
|
|
38
|
+
new(logger)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
alias_method :[], :call
|
|
43
|
+
|
|
44
|
+
# @api private
|
|
45
|
+
def compatible_logger?(logger)
|
|
46
|
+
structured_logger?(logger) && tagged_logger?(logger)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
def tagged_logger?(logger)
|
|
51
|
+
logger.respond_to?(:tagged)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @api private
|
|
55
|
+
def structured_logger?(logger)
|
|
56
|
+
logger.respond_to?(:info) &&
|
|
57
|
+
logger.method(:info).parameters.any? { |(type, _)| type == :keyrest }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api public
|
|
62
|
+
# @since 3.0.0
|
|
63
|
+
attr_reader :logger
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def initialize(logger)
|
|
67
|
+
@logger = logger
|
|
68
|
+
@structured_logger = self.class.structured_logger?(logger)
|
|
69
|
+
@tags_thread_key = :"hanami_universal_logger_tags_#{object_id}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
LOG_LEVEL_METHODS = %i[debug info warn error fatal unknown].freeze
|
|
74
|
+
private_constant :LOG_LEVEL_METHODS
|
|
75
|
+
|
|
76
|
+
# @!method debug(message = nil, **payload, &blk)
|
|
77
|
+
# Logs a debug message.
|
|
78
|
+
#
|
|
79
|
+
# @param message [String, nil] the log message
|
|
80
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
81
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
82
|
+
# @return [void]
|
|
83
|
+
#
|
|
84
|
+
# @api public
|
|
85
|
+
# @since 3.0.0
|
|
86
|
+
|
|
87
|
+
# @!method info(message = nil, **payload, &blk)
|
|
88
|
+
# Logs an info message.
|
|
89
|
+
#
|
|
90
|
+
# @param message [String, nil] the log message
|
|
91
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
92
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
93
|
+
# @return [void]
|
|
94
|
+
# @api public
|
|
95
|
+
# @since 3.0.0
|
|
96
|
+
|
|
97
|
+
# @!method warn(message = nil, **payload, &blk)
|
|
98
|
+
# Logs a warning message.
|
|
99
|
+
#
|
|
100
|
+
# @param message [String, nil] the log message
|
|
101
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
102
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
103
|
+
# @return [void]
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
# @since 3.0.0
|
|
107
|
+
|
|
108
|
+
# @!method error(message = nil, **payload, &blk)
|
|
109
|
+
# Logs an error message.
|
|
110
|
+
#
|
|
111
|
+
# @param message [String, nil] the log message
|
|
112
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
113
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
114
|
+
# @return [void]
|
|
115
|
+
#
|
|
116
|
+
# @api public
|
|
117
|
+
# @since 3.0.0
|
|
118
|
+
|
|
119
|
+
# @!method fatal(message = nil, **payload, &blk)
|
|
120
|
+
# Logs a fatal message.
|
|
121
|
+
#
|
|
122
|
+
# @param message [String, nil] the log message
|
|
123
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
124
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
125
|
+
# @return [void]
|
|
126
|
+
#
|
|
127
|
+
# @api public
|
|
128
|
+
# @since 3.0.0
|
|
129
|
+
|
|
130
|
+
# @!method unknown(message = nil, **payload, &blk)
|
|
131
|
+
# Logs a message with unknown severity.
|
|
132
|
+
#
|
|
133
|
+
# @param message [String, nil] the log message
|
|
134
|
+
# @param payload [Hash] structured data to include in the log entry
|
|
135
|
+
# @yieldreturn [Hash] additional payload data to merge
|
|
136
|
+
# @return [void]
|
|
137
|
+
#
|
|
138
|
+
# @api public
|
|
139
|
+
# @since 3.0.0
|
|
140
|
+
|
|
141
|
+
LOG_LEVEL_METHODS.each do |level|
|
|
142
|
+
define_method(level) do |message = nil, **payload, &blk|
|
|
143
|
+
_log(level, message, payload, &blk)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @api public
|
|
148
|
+
# @since 3.0.0
|
|
149
|
+
def add(severity, message = nil, progname = nil, &blk)
|
|
150
|
+
# Convert severity to a level symbol if it's an integer (the standard Logger uses integers).
|
|
151
|
+
level = _severity_to_level(severity)
|
|
152
|
+
|
|
153
|
+
payload = {}
|
|
154
|
+
payload[:progname] = progname if progname
|
|
155
|
+
|
|
156
|
+
_log(level, message, payload, &blk)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api public
|
|
160
|
+
# @since 3.0.0
|
|
161
|
+
alias_method :log, :add
|
|
162
|
+
|
|
163
|
+
# @api public
|
|
164
|
+
# @since 3.0.0
|
|
165
|
+
def tagged(*tags)
|
|
166
|
+
previous_tags = _current_tags
|
|
167
|
+
self._current_tags = tags
|
|
168
|
+
begin
|
|
169
|
+
yield
|
|
170
|
+
ensure
|
|
171
|
+
self._current_tags = previous_tags
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Delegates any other methods to the wrapped logger.
|
|
178
|
+
def method_missing(method, ...)
|
|
179
|
+
if logger.respond_to?(method)
|
|
180
|
+
logger.public_send(method, ...)
|
|
181
|
+
else
|
|
182
|
+
super
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def respond_to_missing?(method, include_private = false)
|
|
187
|
+
logger.respond_to?(method, include_private) || super
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Maps a standard Logger severity integer (e.g. 1) to a level name (`:info`).
|
|
191
|
+
#
|
|
192
|
+
# We need this be able to support the basic `Logger#log` and `#add` methods in addition to the
|
|
193
|
+
# named severity methods.
|
|
194
|
+
def _severity_to_level(severity)
|
|
195
|
+
return severity if severity.is_a?(Symbol)
|
|
196
|
+
|
|
197
|
+
SEVERITY_MAP.fetch(severity, :unknown)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
SEVERITY_MAP = {
|
|
201
|
+
0 => :debug, # Logger::DEBUG
|
|
202
|
+
1 => :info, # Logger::INFO
|
|
203
|
+
2 => :warn, # Logger::WARN
|
|
204
|
+
3 => :error, # Logger::ERROR
|
|
205
|
+
4 => :fatal, # Logger::FATAL
|
|
206
|
+
5 => :unknown # Logger::UNKNOWN
|
|
207
|
+
}.freeze
|
|
208
|
+
private_constant :SEVERITY_MAP
|
|
209
|
+
|
|
210
|
+
def _log(level, message, payload, &blk)
|
|
211
|
+
if @structured_logger
|
|
212
|
+
_log_structured(level, message, payload, &blk)
|
|
213
|
+
else
|
|
214
|
+
_log_json(level, message, payload, &blk)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def _log_structured(method, message, payload)
|
|
219
|
+
payload = payload.merge(yield) if block_given?
|
|
220
|
+
|
|
221
|
+
tags = _current_tags
|
|
222
|
+
payload[:tags] = tags if tags && !tags.empty?
|
|
223
|
+
|
|
224
|
+
logger.public_send(method, message, **payload)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def _log_json(method, message, payload)
|
|
228
|
+
json_data =
|
|
229
|
+
if block_given?
|
|
230
|
+
yield
|
|
231
|
+
else
|
|
232
|
+
payload[:message] = message if message
|
|
233
|
+
payload
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
tags = _current_tags
|
|
237
|
+
json_data[:tags] = tags if tags && !tags.empty?
|
|
238
|
+
|
|
239
|
+
logger.public_send(method, JSON.generate(json_data))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def _current_tags
|
|
243
|
+
Thread.current[@tags_thread_key]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def _current_tags=(tags)
|
|
247
|
+
Thread.current[@tags_thread_key] = tags
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
# Hanami version
|
|
5
|
+
#
|
|
6
|
+
# @since 0.9.0
|
|
7
|
+
# @api private
|
|
8
|
+
module Version
|
|
9
|
+
# @api public
|
|
10
|
+
VERSION = "3.0.1"
|
|
11
|
+
|
|
12
|
+
# @since 0.9.0
|
|
13
|
+
# @api private
|
|
14
|
+
def self.version
|
|
15
|
+
VERSION
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @since 0.9.0
|
|
19
|
+
# @api private
|
|
20
|
+
def self.gem_requirement
|
|
21
|
+
if prerelease?
|
|
22
|
+
version
|
|
23
|
+
else
|
|
24
|
+
"~> #{major_minor}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @since 0.9.0
|
|
29
|
+
# @api private
|
|
30
|
+
def self.prerelease?
|
|
31
|
+
version =~ /alpha|beta|rc/
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @since 0.9.0
|
|
35
|
+
# @api private
|
|
36
|
+
def self.major_minor
|
|
37
|
+
version.scan(/\A\d{1,2}\.\d{1,2}/).first
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Defines the full version
|
|
42
|
+
#
|
|
43
|
+
# @since 0.1.0
|
|
44
|
+
VERSION = Version.version
|
|
45
|
+
end
|