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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/hanami-3.0.1/CHANGELOG.md +1701 -0
  3. data/hanami-3.0.1/LICENSE +20 -0
  4. data/hanami-3.0.1/README.md +89 -0
  5. data/hanami-3.0.1/hanami.gemspec +49 -0
  6. data/hanami-3.0.1/lib/hanami/app.rb +197 -0
  7. data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
  8. data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
  9. data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
  10. data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
  11. data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
  12. data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
  13. data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
  14. data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
  15. data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
  16. data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
  17. data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
  18. data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
  19. data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
  20. data/hanami-3.0.1/lib/hanami/config.rb +564 -0
  21. data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
  22. data/hanami-3.0.1/lib/hanami/env.rb +52 -0
  23. data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
  24. data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
  25. data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
  26. data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
  27. data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
  28. data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
  29. data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
  30. data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
  31. data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
  32. data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
  33. data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
  34. data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
  35. data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
  36. data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
  37. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
  38. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
  39. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
  40. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
  41. data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
  42. data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
  43. data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
  44. data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
  45. data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
  46. data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
  47. data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
  48. data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
  49. data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
  50. data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
  51. data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
  52. data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
  53. data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
  54. data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
  55. data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
  56. data/hanami-3.0.1/lib/hanami/port.rb +45 -0
  57. data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
  58. data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
  59. data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
  60. data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
  61. data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
  62. data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
  63. data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
  64. data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
  65. data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
  66. data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
  67. data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
  68. data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
  69. data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
  70. data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
  71. data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
  72. data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
  73. data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
  74. data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
  75. data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
  76. data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
  77. data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
  78. data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
  79. data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
  80. data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
  81. data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
  82. data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
  83. data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
  84. data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
  85. data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
  86. data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
  87. data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
  88. data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
  89. data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
  90. data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
  91. data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
  92. data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
  93. data/hanami-3.0.1/lib/hanami/version.rb +45 -0
  94. data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
  95. data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
  96. data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
  97. data/hanami-3.0.1/lib/hanami.rb +276 -0
  98. data/piko-lite-box.gemspec +11 -0
  99. 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