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,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
# @api private
|
|
5
|
+
module Providers
|
|
6
|
+
# Provider source to register Rack integration components in Hanami slices.
|
|
7
|
+
#
|
|
8
|
+
# @see Hanami::Providers::Logger
|
|
9
|
+
# @see Hanami::Web::RackLogger
|
|
10
|
+
# @see https://github.com/rack/rack
|
|
11
|
+
# @see https://dry-rb.org/gems/dry-monitor/
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
# @since 2.0.0
|
|
15
|
+
class Rack < Hanami::Provider::Source
|
|
16
|
+
# @api private
|
|
17
|
+
def prepare
|
|
18
|
+
Dry::Monitor.load_extensions(:rack)
|
|
19
|
+
|
|
20
|
+
# Explicitly register the Rack middleware events on our notifications bus. The Dry::Monitor
|
|
21
|
+
# rack extension (activated above) does register these globally, but if the notifications
|
|
22
|
+
# bus has been used before this provider loads, then it will have created its own separate
|
|
23
|
+
# local copy of all registered events as of that moment in time, which will not be included
|
|
24
|
+
# in the Rack events globally registered above.
|
|
25
|
+
notifications = target["notifications"]
|
|
26
|
+
notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_START)
|
|
27
|
+
notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_STOP)
|
|
28
|
+
notifications.register_event(Dry::Monitor::Rack::Middleware::REQUEST_ERROR)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
def start
|
|
33
|
+
slice.start :logger
|
|
34
|
+
|
|
35
|
+
monitor_middleware = Dry::Monitor::Rack::Middleware.new(
|
|
36
|
+
target["notifications"],
|
|
37
|
+
clock: Dry::Monitor::Clock.new(unit: :microsecond)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
rack_logger = Hanami::Web::RackLogger.new(target[:logger], env: slice.container.env)
|
|
41
|
+
rack_logger.attach(monitor_middleware)
|
|
42
|
+
|
|
43
|
+
register "monitor", monitor_middleware
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
module Providers
|
|
5
|
+
# @api private
|
|
6
|
+
# @since 2.2.0
|
|
7
|
+
class Relations < Hanami::Provider::Source
|
|
8
|
+
def start
|
|
9
|
+
start_and_import_parent_relations and return if slice.parent && slice.config.db.import_from_parent
|
|
10
|
+
|
|
11
|
+
slice.start :db
|
|
12
|
+
|
|
13
|
+
register_relations target["db.rom"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def register_relations(rom)
|
|
19
|
+
rom.relations.each do |name, _|
|
|
20
|
+
register name, rom.relations[name]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def start_and_import_parent_relations
|
|
25
|
+
slice.parent.start :relations
|
|
26
|
+
|
|
27
|
+
register_relations slice.parent["db.rom"]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hanami
|
|
4
|
+
# @api private
|
|
5
|
+
module Providers
|
|
6
|
+
# Provider source to register routes helper component in Hanami slices.
|
|
7
|
+
#
|
|
8
|
+
# @see Hanami::Slice::RoutesHelper
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
# @since 2.0.0
|
|
12
|
+
class Routes < Hanami::Provider::Source
|
|
13
|
+
# @api private
|
|
14
|
+
def prepare
|
|
15
|
+
require "hanami/slice/routes_helper"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
def start
|
|
20
|
+
# Register a lazy instance of RoutesHelper to ensure we don't load prematurely load the
|
|
21
|
+
# router during the process of booting. This ensures the router's resolver can run strict
|
|
22
|
+
# action key checks once when it runs on a fully booted slice.
|
|
23
|
+
register :routes do
|
|
24
|
+
Hanami::Slice::RoutesHelper.new(slice.router)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/cli"
|
|
4
|
+
|
|
5
|
+
Hanami::CLI::RakeTasks.register_tasks do
|
|
6
|
+
desc "Load the app environment"
|
|
7
|
+
task :environment do
|
|
8
|
+
require "hanami/prepare"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Ruby ecosystem compatibility
|
|
12
|
+
#
|
|
13
|
+
# Most of the hosting SaaS automatic tasks are designed after Ruby on Rails.
|
|
14
|
+
# They expect the following Rake tasks to be present:
|
|
15
|
+
#
|
|
16
|
+
# * db:migrate
|
|
17
|
+
# * assets:precompile
|
|
18
|
+
#
|
|
19
|
+
# See https://github.com/heroku/heroku-buildpack-ruby/issues/442
|
|
20
|
+
#
|
|
21
|
+
# ===
|
|
22
|
+
#
|
|
23
|
+
# These Rake tasks are **NOT** listed when someone runs `rake -T`, because we
|
|
24
|
+
# want to encourage developers to use `hanami` CLI commands.
|
|
25
|
+
#
|
|
26
|
+
# In order to migrate the database or compile assets a developer should use:
|
|
27
|
+
#
|
|
28
|
+
# * hanami db migrate
|
|
29
|
+
# * hanami assets compile
|
|
30
|
+
#
|
|
31
|
+
# This is the preferred way to run Hanami command line tasks.
|
|
32
|
+
# Please use them when you're in control of your deployment environment.
|
|
33
|
+
#
|
|
34
|
+
# If you're not in control and your deployment requires these "standard"
|
|
35
|
+
# Rake tasks, they are here only to solve this specific problem.
|
|
36
|
+
|
|
37
|
+
if Hanami.bundled?("hanami-db")
|
|
38
|
+
namespace :db do
|
|
39
|
+
task :migrate do
|
|
40
|
+
Hanami::CLI::Commands::App::DB::Migrate.new.call
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if Hanami.bundled?("hanami-assets")
|
|
46
|
+
namespace :assets do
|
|
47
|
+
task :precompile do
|
|
48
|
+
Hanami::CLI::Commands::App::Assets::Compile.new.call
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Hanami::CLI::RakeTasks.install_tasks
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
require_relative "slice/router"
|
|
6
|
+
|
|
7
|
+
module Hanami
|
|
8
|
+
# App routes
|
|
9
|
+
#
|
|
10
|
+
# Users are expected to inherit from this class to define their app
|
|
11
|
+
# routes.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# # config/routes.rb
|
|
15
|
+
# # frozen_string_literal: true
|
|
16
|
+
#
|
|
17
|
+
# require "hanami/routes"
|
|
18
|
+
#
|
|
19
|
+
# module MyApp
|
|
20
|
+
# class Routes < Hanami::Routes
|
|
21
|
+
# root to: "home.show"
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# See {Hanami::Slice::Router} for the syntax allowed within the `define` block.
|
|
26
|
+
#
|
|
27
|
+
# @see Hanami::Slice::Router
|
|
28
|
+
# @since 2.0.0
|
|
29
|
+
class Routes
|
|
30
|
+
# Error raised when no action could be found in an app or slice container for the key given in a
|
|
31
|
+
# routes file.
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
# @since 2.0.0
|
|
35
|
+
class MissingActionError < Hanami::Error
|
|
36
|
+
# @api private
|
|
37
|
+
def initialize(action_key, slice)
|
|
38
|
+
action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
|
|
39
|
+
action_constant = slice.inflector.camelize(
|
|
40
|
+
slice.inflector.underscore(slice.namespace.to_s) + PATH_DELIMITER + action_path
|
|
41
|
+
)
|
|
42
|
+
action_file_path = slice.relative_source_path.join(action_path).to_s.concat(RB_EXT)
|
|
43
|
+
|
|
44
|
+
super(<<~MSG)
|
|
45
|
+
Could not find action with key #{action_key.inspect} in #{slice}
|
|
46
|
+
|
|
47
|
+
To fix this, define the action class #{action_constant} in #{action_file_path}
|
|
48
|
+
MSG
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Error raised when a given routes endpoint does not implement the `#call` interface required
|
|
53
|
+
# for Rack.
|
|
54
|
+
#
|
|
55
|
+
# @api public
|
|
56
|
+
# @since 2.0.0
|
|
57
|
+
class NotCallableEndpointError < Hanami::Error
|
|
58
|
+
# @api private
|
|
59
|
+
def initialize(endpoint)
|
|
60
|
+
super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Wrapper class for the (otherwise opaque) proc returned from {.routes}, adding an `#empty?`
|
|
65
|
+
# method that returns true if no routes were defined.
|
|
66
|
+
#
|
|
67
|
+
# This is useful when needing to determine behaviour based on the presence of user-defined
|
|
68
|
+
# routes, such as determining whether to show the Hanami welcome page in {Slice#load_router}.
|
|
69
|
+
#
|
|
70
|
+
# @api private
|
|
71
|
+
# @since 2.1.0
|
|
72
|
+
class RoutesProc < DelegateClass(Proc)
|
|
73
|
+
# @api private
|
|
74
|
+
# @since 2.1.0
|
|
75
|
+
def self.empty
|
|
76
|
+
new(proc {}, empty: true)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @api private
|
|
80
|
+
# @since 2.1.0
|
|
81
|
+
def initialize(proc, empty: false)
|
|
82
|
+
@empty = empty
|
|
83
|
+
super(proc)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
# @since 2.1.0
|
|
88
|
+
def empty?
|
|
89
|
+
!!@empty
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @api private
|
|
94
|
+
def self.routes
|
|
95
|
+
@routes ||= build_routes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
# @api private
|
|
100
|
+
def build_routes(definitions = self.definitions)
|
|
101
|
+
return RoutesProc.empty if definitions.empty?
|
|
102
|
+
|
|
103
|
+
routes_proc = proc do
|
|
104
|
+
definitions.each do |(name, args, kwargs, block)|
|
|
105
|
+
if block
|
|
106
|
+
public_send(name, *args, **kwargs, &block)
|
|
107
|
+
else
|
|
108
|
+
public_send(name, *args, **kwargs)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
RoutesProc.new(routes_proc)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @api private
|
|
117
|
+
def definitions
|
|
118
|
+
@definitions ||= []
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# @api private
|
|
124
|
+
def supported_methods
|
|
125
|
+
@supported_methods ||= Slice::Router.public_instance_methods
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @api private
|
|
129
|
+
def respond_to_missing?(name, include_private = false)
|
|
130
|
+
supported_methods.include?(name) || super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Capture all method calls that are supported by the router DSL
|
|
134
|
+
# so that it can be evaluated lazily during configuration/boot
|
|
135
|
+
# process
|
|
136
|
+
#
|
|
137
|
+
# @api private
|
|
138
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
139
|
+
return super unless respond_to?(name)
|
|
140
|
+
|
|
141
|
+
definitions << [name, args, kwargs, block]
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/constants"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
class Settings
|
|
7
|
+
# A settings store that chains multiple stores with fallback resolution.
|
|
8
|
+
#
|
|
9
|
+
# Each store is tried in order. The first store to return a value wins.
|
|
10
|
+
# Stores must implement `#fetch` with the same signature as `Hash#fetch`.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # config/app.rb
|
|
14
|
+
# config.settings_store = Hanami::Settings::CompositeStore.new(
|
|
15
|
+
# Hanami::Settings::EnvStore.new,
|
|
16
|
+
# MyCustomStore.new
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
# @since 3.0.0
|
|
21
|
+
class CompositeStore
|
|
22
|
+
# @api private
|
|
23
|
+
Undefined = Dry::Core::Constants::Undefined
|
|
24
|
+
|
|
25
|
+
# @param stores [Array<#fetch>] ordered list of stores to query
|
|
26
|
+
def initialize(*stores)
|
|
27
|
+
@stores = stores
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Fetches a value by trying each store in order.
|
|
31
|
+
#
|
|
32
|
+
# @param name [String, Symbol] the setting name
|
|
33
|
+
# @param args [Array] optional default value
|
|
34
|
+
# @yield [name] optional block for default value
|
|
35
|
+
# @return [Object] the setting value
|
|
36
|
+
# @raise [KeyError] if no store has the key and no default is given
|
|
37
|
+
#
|
|
38
|
+
# @api public
|
|
39
|
+
# @since 3.0.0
|
|
40
|
+
def fetch(name, *args, &block)
|
|
41
|
+
@stores.each do |store|
|
|
42
|
+
value = store.fetch(name, Undefined)
|
|
43
|
+
return value unless value.equal?(Undefined)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return args.first unless args.empty?
|
|
47
|
+
return yield(name) if block
|
|
48
|
+
|
|
49
|
+
raise KeyError, "key not found: #{name.inspect}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/constants"
|
|
4
|
+
|
|
5
|
+
module Hanami
|
|
6
|
+
class Settings
|
|
7
|
+
# The default store for {Hanami::Settings}, loading setting values from `ENV`.
|
|
8
|
+
#
|
|
9
|
+
# If your app loads the dotenv gem, then `ENV` will also be populated from various `.env` files when
|
|
10
|
+
# you subclass `Hanami::App`.
|
|
11
|
+
#
|
|
12
|
+
# @since 2.0.0
|
|
13
|
+
# @api private
|
|
14
|
+
class EnvStore
|
|
15
|
+
NO_ARG = Object.new.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :store, :hanami_env
|
|
18
|
+
|
|
19
|
+
def initialize(store: ENV, hanami_env: Hanami.env)
|
|
20
|
+
@store = store
|
|
21
|
+
@hanami_env = hanami_env
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fetch(name, default_value = NO_ARG, &block)
|
|
25
|
+
name = name.to_s.upcase
|
|
26
|
+
args = default_value.eql?(NO_ARG) ? [name] : [name, default_value]
|
|
27
|
+
|
|
28
|
+
store.fetch(*args, &block)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/constants"
|
|
4
|
+
require "dry/configurable"
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
|
|
7
|
+
module Hanami
|
|
8
|
+
# Provides user-defined settings for an Hanami app or slice.
|
|
9
|
+
#
|
|
10
|
+
# Define your own settings by inheriting from this class in `config/settings.rb` within an app or
|
|
11
|
+
# slice. Your settings will be loaded from matching ENV vars (with upper-cased names) and be
|
|
12
|
+
# registered as a component as part of the Hanami app {Hanami::Slice::ClassMethods#prepare
|
|
13
|
+
# prepare} step.
|
|
14
|
+
#
|
|
15
|
+
# The settings instance is registered in your app and slice containers as a `"settings"`
|
|
16
|
+
# component. You can use the `Deps` mixin to inject this dependency and make settings available to
|
|
17
|
+
# your other components as required.
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# # config/settings.rb
|
|
21
|
+
# # frozen_string_literal: true
|
|
22
|
+
#
|
|
23
|
+
# module MyApp
|
|
24
|
+
# class Settings < Hanami::Settings
|
|
25
|
+
# Secret = Types::String.constrained(min_size: 20)
|
|
26
|
+
#
|
|
27
|
+
# setting :database_url, constructor: Types::String
|
|
28
|
+
# setting :session_secret, constructor: Secret
|
|
29
|
+
# setting :some_flag, default: false, constructor: Types::Params::Bool
|
|
30
|
+
# end
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# Settings are defined with [dry-configurable][dry-c]'s `setting` method. You may likely want to
|
|
34
|
+
# provide `default:` and `constructor:` options for your settings.
|
|
35
|
+
#
|
|
36
|
+
# If you have [dry-types][dry-t] bundled, then a nested `Types` module will be available for type
|
|
37
|
+
# checking your setting values. Pass type objects to the setting `constructor:` options to ensure
|
|
38
|
+
# their values meet your type expectations. You can use dry-types' default type objects or define
|
|
39
|
+
# your own.
|
|
40
|
+
#
|
|
41
|
+
# When the settings are initialized, all type errors will be collected and presented together for
|
|
42
|
+
# correction. Settings are loaded early, as part of the Hanami app's
|
|
43
|
+
# {Hanami::Slice::ClassMethods#prepare prepare} step, to ensure that the app boots only when valid
|
|
44
|
+
# settings are present.
|
|
45
|
+
#
|
|
46
|
+
# Setting values are loaded from a configurable store, which defaults to
|
|
47
|
+
# {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
|
|
48
|
+
# `ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores
|
|
49
|
+
# must implement a `#fetch` method with the same signature as `Hash#fetch`.
|
|
50
|
+
#
|
|
51
|
+
# [dry-c]: https://dry-rb.org/gems/dry-configurable/
|
|
52
|
+
# [dry-t]: https://dry-rb.org/gems/dry-types/
|
|
53
|
+
#
|
|
54
|
+
# @see Hanami::Settings::DotenvStore
|
|
55
|
+
#
|
|
56
|
+
# @api public
|
|
57
|
+
# @since 2.0.0
|
|
58
|
+
class Settings
|
|
59
|
+
# Error raised when setting values do not meet their type expectations.
|
|
60
|
+
#
|
|
61
|
+
# Its message collects all the individual errors that can be raised for each setting.
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
# @since 2.0.0
|
|
65
|
+
class InvalidSettingsError < Hanami::Error
|
|
66
|
+
# @api private
|
|
67
|
+
def initialize(errors)
|
|
68
|
+
super()
|
|
69
|
+
@errors = errors
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the exception's message.
|
|
73
|
+
#
|
|
74
|
+
# @return [String]
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
# @since 2.0.0
|
|
78
|
+
def to_s
|
|
79
|
+
<<~STR.strip
|
|
80
|
+
Could not initialize settings. The following settings were invalid:
|
|
81
|
+
|
|
82
|
+
#{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
|
|
83
|
+
STR
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class << self
|
|
88
|
+
# Defines a nested `Types` constant in `Settings` subclasses if dry-types is bundled.
|
|
89
|
+
#
|
|
90
|
+
# @see https://dry-rb.org/gems/dry-types
|
|
91
|
+
#
|
|
92
|
+
# @api private
|
|
93
|
+
def inherited(subclass)
|
|
94
|
+
super
|
|
95
|
+
|
|
96
|
+
if Hanami.bundled?("dry-types")
|
|
97
|
+
require "dry/types"
|
|
98
|
+
subclass.const_set(:Types, Dry.Types())
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Loads the settings for a slice.
|
|
103
|
+
#
|
|
104
|
+
# Returns nil if no settings class is defined.
|
|
105
|
+
#
|
|
106
|
+
# @return [Settings, nil]
|
|
107
|
+
#
|
|
108
|
+
# @api private
|
|
109
|
+
def load_for_slice(slice)
|
|
110
|
+
return unless settings_defined?(slice)
|
|
111
|
+
|
|
112
|
+
require_slice_settings(slice) unless slice_settings_class?(slice)
|
|
113
|
+
|
|
114
|
+
slice_settings_class(slice).new(slice.config.settings_store)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# Returns true if settings are defined for the slice.
|
|
120
|
+
#
|
|
121
|
+
# Settings are considered defined if a `Settings` class is already defined in the slice
|
|
122
|
+
# namespace, or a `config/settings.rb` exists under the slice root.
|
|
123
|
+
def settings_defined?(slice)
|
|
124
|
+
slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
|
|
125
|
+
slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def slice_settings_class?(slice)
|
|
129
|
+
slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def slice_settings_class(slice)
|
|
133
|
+
slice.namespace.const_get(SETTINGS_CLASS_NAME)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def require_slice_settings(slice)
|
|
137
|
+
require "hanami/settings"
|
|
138
|
+
|
|
139
|
+
slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)
|
|
140
|
+
|
|
141
|
+
begin
|
|
142
|
+
require slice_settings_require_path
|
|
143
|
+
rescue LoadError => exception
|
|
144
|
+
raise exception unless exception.path == slice_settings_require_path
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @api private
|
|
150
|
+
Undefined = Dry::Core::Constants::Undefined
|
|
151
|
+
|
|
152
|
+
# @api private
|
|
153
|
+
EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
|
|
154
|
+
|
|
155
|
+
include Dry::Configurable
|
|
156
|
+
|
|
157
|
+
# @api private
|
|
158
|
+
def initialize(store = EMPTY_STORE)
|
|
159
|
+
errors = config._settings.map(&:name).each_with_object({}) do |name, errs|
|
|
160
|
+
value = store.fetch(name, Undefined)
|
|
161
|
+
|
|
162
|
+
if value.eql?(Undefined)
|
|
163
|
+
# When a key is missing entirely from the store, _read_ its value from the config instead.
|
|
164
|
+
# This ensures its setting constructor runs (with a `nil` argument given) and raises any
|
|
165
|
+
# necessary errors.
|
|
166
|
+
public_send(name)
|
|
167
|
+
else
|
|
168
|
+
public_send("#{name}=", value)
|
|
169
|
+
end
|
|
170
|
+
rescue => exception # rubocop:disable Style/RescueStandardError
|
|
171
|
+
errs[name] = exception
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
raise InvalidSettingsError, errors if errors.any?
|
|
175
|
+
|
|
176
|
+
config.finalize!
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns a string containing a human-readable representation of the settings.
|
|
180
|
+
#
|
|
181
|
+
# This includes setting names only, not any values, to ensure that sensitive values do not
|
|
182
|
+
# inadvertently leak.
|
|
183
|
+
#
|
|
184
|
+
# Use {#inspect_values} to inspect settings with their values.
|
|
185
|
+
#
|
|
186
|
+
# @example
|
|
187
|
+
# settings.inspect
|
|
188
|
+
# # => #<MyApp::Settings [database_url, session_secret, some_flag]>
|
|
189
|
+
#
|
|
190
|
+
# @return [String]
|
|
191
|
+
#
|
|
192
|
+
# @see #inspect_values
|
|
193
|
+
#
|
|
194
|
+
# @api public
|
|
195
|
+
# @since 2.0.0
|
|
196
|
+
def inspect
|
|
197
|
+
"#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# rubocop:disable Layout/LineLength
|
|
201
|
+
|
|
202
|
+
# Returns a string containing a human-readable representation of the settings and their values.
|
|
203
|
+
#
|
|
204
|
+
# @example
|
|
205
|
+
# settings.inspect_values
|
|
206
|
+
# # => #<MyApp::Settings database_url="postgres://localhost/my_db", session_secret="xxx", some_flag=true]>
|
|
207
|
+
#
|
|
208
|
+
# @return [String]
|
|
209
|
+
#
|
|
210
|
+
# @see #inspect
|
|
211
|
+
#
|
|
212
|
+
# @api public
|
|
213
|
+
# @since 2.0.0
|
|
214
|
+
def inspect_values
|
|
215
|
+
"#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# rubocop:enable Layout/LineLength
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def method_missing(name, *args, &block)
|
|
223
|
+
if config.respond_to?(name)
|
|
224
|
+
config.send(name, *args, &block)
|
|
225
|
+
else
|
|
226
|
+
super
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def respond_to_missing?(name, _include_all = false)
|
|
231
|
+
config.respond_to?(name) || super
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|