hanami 2.0.0.alpha1 → 2.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
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