hanami 2.0.0.alpha1 → 2.0.0.alpha2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -5
  3. data/FEATURES.md +9 -1
  4. data/LICENSE.md +1 -1
  5. data/README.md +4 -5
  6. data/hanami.gemspec +11 -11
  7. data/lib/hanami.rb +19 -18
  8. data/lib/hanami/application.rb +322 -26
  9. data/lib/hanami/application/autoloader/inflector_adapter.rb +22 -0
  10. data/lib/hanami/application/container/boot/inflector.rb +7 -0
  11. data/lib/hanami/application/container/boot/logger.rb +8 -0
  12. data/lib/hanami/application/container/boot/rack_logger.rb +19 -0
  13. data/lib/hanami/application/container/boot/rack_monitor.rb +12 -0
  14. data/lib/hanami/application/container/boot/settings.rb +7 -0
  15. data/lib/hanami/application/router.rb +59 -0
  16. data/lib/hanami/application/routing/middleware/stack.rb +89 -0
  17. data/lib/hanami/application/routing/resolver.rb +82 -0
  18. data/lib/hanami/application/routing/resolver/node.rb +50 -0
  19. data/lib/hanami/application/routing/resolver/trie.rb +59 -0
  20. data/lib/hanami/application/settings.rb +23 -0
  21. data/lib/hanami/application/settings/definition.rb +26 -0
  22. data/lib/hanami/application/settings/loader.rb +97 -0
  23. data/lib/hanami/application/settings/struct.rb +65 -0
  24. data/lib/hanami/boot.rb +1 -2
  25. data/lib/hanami/cli/application/cli.rb +40 -0
  26. data/lib/hanami/cli/application/command.rb +47 -0
  27. data/lib/hanami/cli/application/commands.rb +16 -0
  28. data/lib/hanami/cli/application/commands/console.rb +81 -0
  29. data/lib/hanami/cli/base_command.rb +48 -0
  30. data/lib/hanami/cli/commands.rb +3 -2
  31. data/lib/hanami/cli/commands/command.rb +4 -4
  32. data/lib/hanami/configuration.rb +129 -64
  33. data/lib/hanami/configuration/middleware.rb +2 -2
  34. data/lib/hanami/configuration/router.rb +50 -0
  35. data/lib/hanami/init.rb +5 -0
  36. data/lib/hanami/setup.rb +9 -0
  37. data/lib/hanami/slice.rb +138 -0
  38. data/lib/hanami/version.rb +1 -1
  39. data/lib/hanami/web/rack_logger.rb +96 -0
  40. metadata +92 -54
  41. data/bin/hanami +0 -8
  42. data/lib/hanami/configuration/cookies.rb +0 -24
  43. data/lib/hanami/configuration/security.rb +0 -141
  44. data/lib/hanami/container.rb +0 -107
  45. data/lib/hanami/frameworks.rb +0 -28
  46. data/lib/hanami/routes.rb +0 -31
@@ -15,7 +15,7 @@ module Hanami
15
15
  # Abstract command
16
16
  #
17
17
  # @since 1.1.0
18
- class Command < Hanami::CLI::Command
18
+ class Command < Dry::CLI::Command
19
19
  # @since 1.1.0
20
20
  # @api private
21
21
  def self.inherited(component)
@@ -72,9 +72,9 @@ module Hanami
72
72
  def call(**options)
73
73
  # FIXME: merge ENV vars (like HANAMI_ENV) into **options
74
74
  super(options)
75
- rescue StandardError => e
76
- warn e.message
77
- warn e.backtrace.join("\n\t")
75
+ rescue StandardError => exception
76
+ warn exception.message
77
+ warn exception.backtrace.join("\n\t")
78
78
  exit(1)
79
79
  end
80
80
  end
@@ -4,6 +4,8 @@ require "uri"
4
4
  require "concurrent/hash"
5
5
  require "concurrent/array"
6
6
  require "dry/inflector"
7
+ require "pathname"
8
+ require "zeitwerk"
7
9
 
8
10
  module Hanami
9
11
  # Hanami application configuration
@@ -12,40 +14,72 @@ module Hanami
12
14
  #
13
15
  # rubocop:disable Metrics/ClassLength
14
16
  class Configuration
15
- require "hanami/configuration/cookies"
16
- require "hanami/configuration/sessions"
17
- require "hanami/configuration/middleware"
18
- require "hanami/configuration/security"
17
+ require_relative "configuration/middleware"
18
+ require_relative "configuration/router"
19
+ require_relative "configuration/sessions"
19
20
 
20
- # rubocop:disable Metrics/MethodLength
21
+ attr_reader :actions
22
+ attr_reader :views
23
+
24
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
21
25
  def initialize(env:)
22
26
  @settings = Concurrent::Hash.new
23
27
 
28
+ self.autoloader = Zeitwerk::Loader.new
29
+
24
30
  self.env = env
25
31
  self.environments = DEFAULT_ENVIRONMENTS.clone
26
32
 
33
+ self.root = Dir.pwd
34
+ self.slices_dir = DEFAULT_SLICES_DIR
35
+ settings[:slices] = {}
36
+
37
+ self.settings_path = DEFAULT_SETTINGS_PATH
38
+ self.settings_loader_options = {}
39
+
27
40
  self.base_url = DEFAULT_BASE_URL
28
41
 
29
42
  self.logger = DEFAULT_LOGGER.clone
30
- self.routes = DEFAULT_ROUTES
31
- self.cookies = DEFAULT_COOKIES
43
+ self.rack_logger_filter_params = DEFAULT_RACK_LOGGER_FILTER_PARAMS.clone
32
44
  self.sessions = DEFAULT_SESSIONS
33
45
 
34
- self.default_request_format = DEFAULT_REQUEST_FORMAT
35
- self.default_response_format = DEFAULT_RESPONSE_FORMAT
36
-
46
+ self.router = Router.new(base_url)
37
47
  self.middleware = Middleware.new
38
- self.security = Security.new
39
48
 
40
49
  self.inflections = Dry::Inflector.new
50
+
51
+ @actions = begin
52
+ require_path = "hanami/action/application_configuration"
53
+ require require_path
54
+ Hanami::Action::ApplicationConfiguration.new
55
+ rescue LoadError => e
56
+ raise e unless e.path == require_path
57
+ Object.new
58
+ end
59
+
60
+ @views = begin
61
+ require_path = "hanami/view/application_configuration"
62
+ require require_path
63
+ Hanami::View::ApplicationConfiguration.new
64
+ rescue LoadError => e
65
+ raise e unless e.path == require_path
66
+ Object.new
67
+ end
41
68
  end
42
- # rubocop:enable Metrics/MethodLength
69
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
43
70
 
44
71
  def finalize
45
72
  environment_for(env).each do |blk|
46
73
  instance_eval(&blk)
47
74
  end
48
75
 
76
+ # Finalize nested configuration
77
+ #
78
+ # TODO: would be good to just create empty configurations for actions/views
79
+ # instead of plain objects
80
+ actions.finalize! if actions.respond_to?(:finalize!)
81
+ views.finalize! if views.respond_to?(:finalize!)
82
+
49
83
  self
50
84
  end
51
85
 
@@ -53,6 +87,14 @@ module Hanami
53
87
  environment_for(name).push(blk)
54
88
  end
55
89
 
90
+ def autoloader=(loader)
91
+ settings[:autoloader] = loader || nil
92
+ end
93
+
94
+ def autoloader
95
+ settings.fetch(:autoloader)
96
+ end
97
+
56
98
  def env=(value)
57
99
  settings[:env] = value
58
100
  end
@@ -61,6 +103,65 @@ module Hanami
61
103
  settings.fetch(:env)
62
104
  end
63
105
 
106
+ def root=(root)
107
+ settings[:root] = Pathname(root)
108
+ end
109
+
110
+ def root
111
+ settings.fetch(:root)
112
+ end
113
+
114
+ def slices_dir=(dir)
115
+ settings[:slices_dir] = dir
116
+ end
117
+
118
+ def slices_dir
119
+ settings.fetch(:slices_dir)
120
+ end
121
+
122
+ def slices_namespace=(namespace)
123
+ settings[:slices_namespace] = namespace
124
+ end
125
+
126
+ def slices_namespace
127
+ settings.fetch(:slices_namespace) { Object }
128
+ end
129
+
130
+ def slice(slice_name, &block)
131
+ settings[:slices][slice_name] = block
132
+ end
133
+
134
+ def slices
135
+ settings[:slices]
136
+ end
137
+
138
+ def settings_path=(value)
139
+ settings[:settings_path] = value
140
+ end
141
+
142
+ def settings_path
143
+ settings.fetch(:settings_path)
144
+ end
145
+
146
+ def settings_loader=(loader)
147
+ settings[:settings_loader] = loader
148
+ end
149
+
150
+ def settings_loader
151
+ settings.fetch(:settings_loader) {
152
+ require "hanami/application/settings/loader"
153
+ settings[:settings_loader] = Application::Settings::Loader
154
+ }
155
+ end
156
+
157
+ def settings_loader_options=(options)
158
+ settings[:settings_loader_options] = options
159
+ end
160
+
161
+ def settings_loader_options
162
+ settings[:settings_loader_options]
163
+ end
164
+
64
165
  def base_url=(value)
65
166
  settings[:base_url] = URI.parse(value)
66
167
  end
@@ -77,20 +178,20 @@ module Hanami
77
178
  settings.fetch(:logger)
78
179
  end
79
180
 
80
- def routes=(value)
81
- settings[:routes] = value
181
+ def rack_logger_filter_params=(params)
182
+ settings[:rack_logger_filter_params] = params
82
183
  end
83
184
 
84
- def routes
85
- settings.fetch(:routes)
185
+ def rack_logger_filter_params
186
+ settings[:rack_logger_filter_params]
86
187
  end
87
188
 
88
- def cookies=(options)
89
- settings[:cookies] = Cookies.new(options)
189
+ def router=(value)
190
+ settings[:router] = value
90
191
  end
91
192
 
92
- def cookies
93
- settings.fetch(:cookies)
193
+ def router
194
+ settings.fetch(:router)
94
195
  end
95
196
 
96
197
  def sessions=(*args)
@@ -101,34 +202,10 @@ module Hanami
101
202
  settings.fetch(:sessions)
102
203
  end
103
204
 
104
- def default_request_format=(value)
105
- settings[:default_request_format] = value
106
- end
107
-
108
- def default_request_format
109
- settings.fetch(:default_request_format)
110
- end
111
-
112
- def default_response_format=(value)
113
- settings[:default_response_format] = value
114
- end
115
-
116
- def default_response_format
117
- settings.fetch(:default_response_format)
118
- end
119
-
120
205
  def middleware
121
206
  settings.fetch(:middleware)
122
207
  end
123
208
 
124
- def security=(value)
125
- settings[:security] = value
126
- end
127
-
128
- def security
129
- settings.fetch(:security)
130
- end
131
-
132
209
  def inflections(&blk)
133
210
  if blk.nil?
134
211
  settings.fetch(:inflections)
@@ -137,16 +214,7 @@ module Hanami
137
214
  end
138
215
  end
139
216
 
140
- def router_settings
141
- bu = base_url
142
-
143
- {
144
- scheme: bu.scheme,
145
- host: bu.host,
146
- port: bu.port,
147
- inflector: inflections
148
- }
149
- end
217
+ alias inflector inflections
150
218
 
151
219
  def for_each_middleware(&blk)
152
220
  stack = middleware.stack.dup
@@ -178,27 +246,24 @@ module Hanami
178
246
  DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
179
247
  private_constant :DEFAULT_ENVIRONMENTS
180
248
 
249
+ DEFAULT_SLICES_DIR = "slices"
250
+ private_constant :DEFAULT_SLICES_DIR
251
+
181
252
  DEFAULT_BASE_URL = "http://0.0.0.0:2300"
182
253
  private_constant :DEFAULT_BASE_URL
183
254
 
184
255
  DEFAULT_LOGGER = { level: :debug }.freeze
185
256
  private_constant :DEFAULT_LOGGER
186
257
 
187
- DEFAULT_ROUTES = File.join("config", "routes")
188
- private_constant :DEFAULT_ROUTES
258
+ DEFAULT_RACK_LOGGER_FILTER_PARAMS = %w[_csrf password password_confirmation].freeze
259
+ private_constant :DEFAULT_RACK_LOGGER_FILTER_PARAMS
189
260
 
190
- DEFAULT_COOKIES = Cookies.null
191
- private_constant :DEFAULT_COOKIES
261
+ DEFAULT_SETTINGS_PATH = File.join("config", "settings")
262
+ private_constant :DEFAULT_SETTINGS_PATH
192
263
 
193
264
  DEFAULT_SESSIONS = Sessions.null
194
265
  private_constant :DEFAULT_SESSIONS
195
266
 
196
- DEFAULT_REQUEST_FORMAT = :html
197
- private_constant :DEFAULT_REQUEST_FORMAT
198
-
199
- DEFAULT_RESPONSE_FORMAT = :html
200
- private_constant :DEFAULT_RESPONSE_FORMAT
201
-
202
267
  attr_reader :settings
203
268
  end
204
269
  # rubocop:enable Metrics/ClassLength
@@ -10,8 +10,8 @@ module Hanami
10
10
  @stack = []
11
11
  end
12
12
 
13
- def use(middleware, *args)
14
- stack.push([middleware, *args])
13
+ def use(middleware, *args, &block)
14
+ stack.push([middleware, *args, block].compact)
15
15
  end
16
16
 
17
17
  attr_reader :stack
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Configuration
5
+ # Hanami router configuration
6
+ #
7
+ # @since 2.0.0
8
+ # @api private
9
+ class Router
10
+ # @api private
11
+ # @since 2.0.0
12
+ attr_writer :routes
13
+
14
+ # @api private
15
+ # @since 2.0.0
16
+ attr_reader :routes
17
+
18
+ # @api private
19
+ # @since 2.0.0
20
+ attr_writer :resolver
21
+
22
+ # @api private
23
+ # @since 2.0.0
24
+ def initialize(base_url, routes: DEFAULT_ROUTES)
25
+ @base_url = base_url
26
+ @routes = routes
27
+ end
28
+
29
+ # @api private
30
+ # @since 2.0.0
31
+ def resolver
32
+ @resolver ||= begin
33
+ require_relative "../application/routing/resolver"
34
+ Application::Routing::Resolver
35
+ end
36
+ end
37
+
38
+ # @api private
39
+ # @since 2.0.0
40
+ def options
41
+ { base_url: @base_url }
42
+ end
43
+
44
+ # @api private
45
+ # @since 2.0.0
46
+ DEFAULT_ROUTES = File.join("config", "routes")
47
+ private_constant :DEFAULT_ROUTES
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ Hanami.init
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "hanami"
5
+
6
+ begin
7
+ require File.join(Dir.pwd, "config/application")
8
+ rescue LoadError # rubocop:disable Lint/SuppressedException
9
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/container"
4
+ require "pathname"
5
+
6
+ module Hanami
7
+ # Distinct area of concern within an Hanami application
8
+ #
9
+ # @since 2.0.0
10
+ class Slice
11
+ attr_reader :application, :name, :namespace, :root
12
+
13
+ def initialize(application, name:, namespace: nil, root: nil, container: nil)
14
+ @application = application
15
+ @name = name.to_sym
16
+ @namespace = namespace
17
+ @root = root ? Pathname(root) : root
18
+ @container = container || define_container
19
+ end
20
+
21
+ def inflector
22
+ application.inflector
23
+ end
24
+
25
+ def namespace_path
26
+ @namespace_path ||= inflector.underscore(namespace.to_s)
27
+ end
28
+
29
+ def init
30
+ container.import application: application.container
31
+
32
+ slice_block = application.configuration.slices[name]
33
+ instance_eval(&slice_block) if slice_block
34
+ end
35
+
36
+ def boot
37
+ container.finalize! do
38
+ container.config.env = application.container.config.env
39
+ end
40
+
41
+ @booted = true
42
+ self
43
+ end
44
+
45
+ # rubocop:disable Style/DoubleNegation
46
+ def booted?
47
+ !!@booted
48
+ end
49
+ # rubocop:enable Style/DoubleNegation
50
+
51
+ def container
52
+ @container ||= define_container
53
+ end
54
+
55
+ def import(*slice_names)
56
+ raise "Cannot import after booting" if booted?
57
+
58
+ slice_names.each do |slice_name|
59
+ container.import slice_name.to_sym => application.slices.fetch(slice_name.to_sym).container
60
+ end
61
+ end
62
+
63
+ def register(*args, &block)
64
+ container.register(*args, &block)
65
+ end
66
+
67
+ def register_bootable(*args, &block)
68
+ container.boot(*args, &block)
69
+ end
70
+
71
+ def init_bootable(*args)
72
+ container.init(*args)
73
+ end
74
+
75
+ def start_bootable(*args)
76
+ container.start(*args)
77
+ end
78
+
79
+ def key?(*args)
80
+ container.key?(*args)
81
+ end
82
+
83
+ def keys
84
+ container.keys
85
+ end
86
+
87
+ def [](*args)
88
+ container[*args]
89
+ end
90
+
91
+ def resolve(*args)
92
+ container.resolve(*args)
93
+ end
94
+
95
+ private
96
+
97
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
98
+ def define_container
99
+ container = Class.new(Dry::System::Container)
100
+ container.use :env
101
+
102
+ container.configure do |config|
103
+ config.name = name
104
+ config.inflector = application.configuration.inflector
105
+
106
+ if application.configuration.autoloader
107
+ require "dry/system/loader/autoloading"
108
+ config.component_dirs.loader = Dry::System::Loader::Autoloading
109
+ config.component_dirs.add_to_load_path = false
110
+ end
111
+
112
+ if root&.directory?
113
+ config.root = root
114
+ config.bootable_dirs = ["config/boot"]
115
+
116
+ if root.join("lib").directory?
117
+ config.component_dirs.add "lib" do |dir|
118
+ dir.default_namespace = namespace_path.tr(File::SEPARATOR, config.namespace_separator)
119
+ end
120
+
121
+ application.configuration.autoloader&.push_dir(root.join("lib"))
122
+ end
123
+ end
124
+ end
125
+
126
+ # Force after configure hook to run
127
+ container.configure do; end
128
+
129
+ if namespace
130
+ namespace.const_set :Container, container
131
+ namespace.const_set :Deps, container.injector
132
+ end
133
+
134
+ container
135
+ end
136
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
137
+ end
138
+ end