hanami 2.0.0.alpha1 → 2.0.0.alpha5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +306 -5
  3. data/FEATURES.md +9 -1
  4. data/LICENSE.md +1 -1
  5. data/README.md +9 -6
  6. data/hanami.gemspec +12 -11
  7. data/lib/hanami/application/autoloader/inflector_adapter.rb +22 -0
  8. data/lib/hanami/application/container/boot/inflector.rb +7 -0
  9. data/lib/hanami/application/container/boot/logger.rb +7 -0
  10. data/lib/hanami/application/container/boot/rack_logger.rb +19 -0
  11. data/lib/hanami/application/container/boot/rack_monitor.rb +12 -0
  12. data/lib/hanami/application/container/boot/routes_helper.rb +9 -0
  13. data/lib/hanami/application/container/boot/settings.rb +7 -0
  14. data/lib/hanami/application/router.rb +59 -0
  15. data/lib/hanami/application/routes.rb +55 -0
  16. data/lib/hanami/application/routes_helper.rb +34 -0
  17. data/lib/hanami/application/routing/middleware/stack.rb +89 -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/routing/resolver.rb +87 -0
  21. data/lib/hanami/application/routing/router.rb +36 -0
  22. data/lib/hanami/application/settings/dotenv_store.rb +60 -0
  23. data/lib/hanami/application/settings.rb +93 -0
  24. data/lib/hanami/application.rb +330 -34
  25. data/lib/hanami/assets/application_configuration.rb +63 -0
  26. data/lib/hanami/assets/configuration.rb +54 -0
  27. data/lib/hanami/boot/source_dirs.rb +44 -0
  28. data/lib/hanami/boot.rb +1 -2
  29. data/lib/hanami/cli/application/cli.rb +40 -0
  30. data/lib/hanami/cli/application/command.rb +47 -0
  31. data/lib/hanami/cli/application/commands/console.rb +81 -0
  32. data/lib/hanami/cli/application/commands.rb +16 -0
  33. data/lib/hanami/cli/base_command.rb +48 -0
  34. data/lib/hanami/cli/commands/command.rb +4 -4
  35. data/lib/hanami/cli/commands.rb +3 -2
  36. data/lib/hanami/configuration/logger.rb +84 -0
  37. data/lib/hanami/configuration/middleware.rb +4 -4
  38. data/lib/hanami/configuration/null_configuration.rb +14 -0
  39. data/lib/hanami/configuration/router.rb +52 -0
  40. data/lib/hanami/configuration/sessions.rb +5 -5
  41. data/lib/hanami/configuration/source_dirs.rb +42 -0
  42. data/lib/hanami/configuration.rb +122 -131
  43. data/lib/hanami/init.rb +5 -0
  44. data/lib/hanami/setup.rb +9 -0
  45. data/lib/hanami/slice.rb +189 -0
  46. data/lib/hanami/version.rb +1 -1
  47. data/lib/hanami/web/rack_logger.rb +96 -0
  48. data/lib/hanami.rb +17 -30
  49. metadata +116 -50
  50. data/bin/hanami +0 -8
  51. data/lib/hanami/configuration/cookies.rb +0 -24
  52. data/lib/hanami/configuration/security.rb +0 -141
  53. data/lib/hanami/container.rb +0 -107
  54. data/lib/hanami/frameworks.rb +0 -28
  55. data/lib/hanami/routes.rb +0 -31
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Application
5
+ # Application routes
6
+ #
7
+ # Users are expected to inherit from this class to define their application
8
+ # routes.
9
+ #
10
+ # @example
11
+ # # config/routes.rb
12
+ # # frozen_string_literal: true
13
+ #
14
+ # require "hanami/application/routes"
15
+ #
16
+ # module MyApp
17
+ # class Routes < Hanami::Application::Routes
18
+ # define do
19
+ # slice :main, at: "/" do
20
+ # root to: "home.show"
21
+ # end
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ # See {Hanami::Application::Router} for the syntax allowed within the
27
+ # `define` block.
28
+ #
29
+ # @see Hanami::Application::Router
30
+ # @since 2.0.0
31
+ class Routes
32
+ # Defines application routes
33
+ #
34
+ # @yield DSL syntax to define application routes executed in the context
35
+ # of {Hanami::Application::Router}
36
+ #
37
+ # @return [Proc]
38
+ def self.define(&block)
39
+ @_routes = block
40
+ end
41
+
42
+ # @api private
43
+ def self.routes
44
+ @_routes || raise(<<~MSG)
45
+ Routes need to be defined before being able to fetch them. E.g.,
46
+ define do
47
+ slice :main, at: "/" do
48
+ root to: "home.show"
49
+ end
50
+ end
51
+ MSG
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Application
5
+ # Hanami application routes helpers
6
+ #
7
+ # An instance of this class gets registered in the container
8
+ # (`routes_helper` key) once the Hanami application is booted. You can use
9
+ # it to get the route helpers for your application.
10
+ #
11
+ # @example
12
+ # MyApp::Application["routes_helper"].path(:root) # => "/"
13
+ #
14
+ # @see Hanami::Router::UrlHelpers
15
+ # @since 2.0.0
16
+ class RoutesHelper
17
+ # @since 2.0.0
18
+ # @api private
19
+ def initialize(router)
20
+ @router = router
21
+ end
22
+
23
+ # @see Hanami::Router::UrlHelpers#path
24
+ def path(*args, **kwargs, &block)
25
+ @router.path(*args, **kwargs, &block)
26
+ end
27
+
28
+ # @see Hanami::Router::UrlHelpers#url
29
+ def url(*args, **kwargs, &block)
30
+ @router.url(*args, **kwargs, &block)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/builder"
4
+
5
+ module Hanami
6
+ class Application
7
+ module Routing
8
+ # Hanami::Applicatione::Router middleware stack
9
+ #
10
+ # @since 2.0.0
11
+ # @api private
12
+ module Middleware
13
+ # Middleware stack
14
+ #
15
+ # @since 2.0.0
16
+ # @api private
17
+ class Stack
18
+ # @since 2.0.0
19
+ # @api private
20
+ ROOT_PREFIX = "/"
21
+ private_constant :ROOT_PREFIX
22
+
23
+ # @since 2.0.0
24
+ # @api private
25
+ def initialize
26
+ @prefix = ROOT_PREFIX
27
+ @stack = Hash.new { |hash, key| hash[key] = [] }
28
+ end
29
+
30
+ # @since 2.0.0
31
+ # @api private
32
+ def use(middleware, *args, &blk)
33
+ @stack[@prefix].push([middleware, args, blk])
34
+ end
35
+
36
+ # @since 2.0.0
37
+ # @api private
38
+ def with(path)
39
+ prefix = @prefix
40
+ @prefix = path
41
+ yield
42
+ ensure
43
+ @prefix = prefix
44
+ end
45
+
46
+ # @since 2.0.0
47
+ # @api private
48
+ def to_rack_app(app) # rubocop:disable Metrics/MethodLength
49
+ s = self
50
+
51
+ Rack::Builder.new do
52
+ s.each do |prefix, stack|
53
+ s.mapped(self, prefix) do
54
+ stack.each do |middleware, args, blk|
55
+ use(middleware, *args, &blk)
56
+ end
57
+ end
58
+
59
+ run app
60
+ end
61
+ end.to_app
62
+ end
63
+
64
+ # @since 2.0.0
65
+ # @api private
66
+ def empty?
67
+ @stack.empty?
68
+ end
69
+
70
+ # @since 2.0.0
71
+ # @api private
72
+ def each(&blk)
73
+ @stack.each(&blk)
74
+ end
75
+
76
+ # @since 2.0.0
77
+ # @api private
78
+ def mapped(builder, prefix, &blk)
79
+ if prefix == ROOT_PREFIX
80
+ builder.instance_eval(&blk)
81
+ else
82
+ builder.map(prefix, &blk)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Application
5
+ module Routing
6
+ class Resolver
7
+ # Endpoint resolver node to register slices in a tree
8
+ #
9
+ # @api private
10
+ # @since 2.0.0
11
+ class Node
12
+ # @api private
13
+ # @since 2.0.0
14
+ attr_reader :slice
15
+
16
+ # @api private
17
+ # @since 2.0.0
18
+ def initialize
19
+ @slice = nil
20
+ @children = {}
21
+ end
22
+
23
+ # @api private
24
+ # @since 2.0.0
25
+ def put(segment)
26
+ @children[segment] ||= self.class.new
27
+ end
28
+
29
+ # @api private
30
+ # @since 2.0.0
31
+ def get(segment)
32
+ @children.fetch(segment) { self if leaf? }
33
+ end
34
+
35
+ # @api private
36
+ # @since 2.0.0
37
+ def leaf!(slice)
38
+ @slice = slice
39
+ end
40
+
41
+ # @api private
42
+ # @since 2.0.0
43
+ def leaf?
44
+ @slice
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/application/routing/resolver/node"
4
+
5
+ module Hanami
6
+ class Application
7
+ module Routing
8
+ class Resolver
9
+ # Endpoint resolver trie to register slices
10
+ #
11
+ # @api private
12
+ # @since 2.0.0
13
+ class Trie
14
+ # @api private
15
+ # @since 2.0.0
16
+ def initialize
17
+ @root = Node.new
18
+ end
19
+
20
+ # @api private
21
+ # @since 2.0.0
22
+ def add(path, name)
23
+ node = @root
24
+ for_each_segment(path) do |segment|
25
+ node = node.put(segment)
26
+ end
27
+
28
+ node.leaf!(name)
29
+ end
30
+
31
+ # @api private
32
+ # @since 2.0.0
33
+ def find(path)
34
+ node = @root
35
+
36
+ for_each_segment(path) do |segment|
37
+ break unless node
38
+
39
+ node = node.get(segment)
40
+ end
41
+
42
+ return node.slice if node&.leaf?
43
+
44
+ nil
45
+ end
46
+
47
+ private
48
+
49
+ # @api private
50
+ # @since 2.0.0
51
+ def for_each_segment(path, &blk)
52
+ _, *segments = path.split(/\//)
53
+ segments.each(&blk)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Application
5
+ module Routing
6
+ # Hanami application router endpoint resolver
7
+ #
8
+ # @since 2.0.0
9
+ class Resolver
10
+ ENDPOINT_KEY_NAMESPACE = "actions"
11
+
12
+ require_relative "resolver/trie"
13
+
14
+ # @since 2.0.0
15
+ class NotCallableEndpointError < StandardError
16
+ def initialize(endpoint)
17
+ super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
18
+ end
19
+ end
20
+
21
+ # @api private
22
+ # @since 2.0.0
23
+ def initialize(slices:, inflector:)
24
+ @slices = slices
25
+ @inflector = inflector
26
+ @slice_registry = Trie.new
27
+ end
28
+
29
+ # @api private
30
+ # @since 2.0.0
31
+ #
32
+ # rubocop:disable Metrics/MethodLength
33
+ def call(path, identifier)
34
+ endpoint =
35
+ case identifier
36
+ when String
37
+ resolve_string_identifier(path, identifier)
38
+ when Class
39
+ identifier.respond_to?(:call) ? identifier : identifier.new
40
+ else
41
+ identifier
42
+ end
43
+
44
+ unless endpoint.respond_to?(:call) # rubocop:disable Style/IfUnlessModifier
45
+ raise NotCallableEndpointError.new(endpoint)
46
+ end
47
+
48
+ endpoint
49
+ end
50
+ # rubocop:enable Metrics/MethodLength
51
+
52
+ # @api private
53
+ # @since 2.0.0
54
+ def register_slice_at_path(name, path)
55
+ slice_registry.add(path, name)
56
+ end
57
+
58
+ private
59
+
60
+ # @api private
61
+ # @since 2.0.0
62
+ attr_reader :slices
63
+
64
+ # @api private
65
+ # @since 2.0.0
66
+ attr_reader :inflector
67
+
68
+ # @api private
69
+ # @since 2.0.0
70
+ attr_reader :slice_registry
71
+
72
+ # @api private
73
+ # @since 2.0.0
74
+ def resolve_string_identifier(path, identifier)
75
+ slice_name = slice_registry.find(path) or raise "missing slice for #{path.inspect} (#{identifier.inspect})"
76
+ slice = slices[slice_name]
77
+ endpoint_key = "#{ENDPOINT_KEY_NAMESPACE}.#{identifier}"
78
+
79
+ # Lazily resolve endpoint from the slice to reduce router initialization time,
80
+ # and break potential endless loops from the resolved endpoint itself requiring
81
+ # access to router-related concerns
82
+ -> (*args) { slice[endpoint_key].call(*args) }
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,36 @@
1
+ # # frozen_string_literal: true
2
+
3
+ # require "hanami/application/router"
4
+
5
+ # Hanami.application.register_bootable :router do
6
+ # start do
7
+ # configuration = Hanami.application.configuration
8
+
9
+ # routes = begin
10
+ # require File.join(configuration.root, configuration.router.routes_path)
11
+ # routes_class = Hanami.application.send(:autodiscover_application_constant, configuration.router.routes_class_name) # WIP private
12
+ # routes_class.routes
13
+ # rescue LoadError
14
+ # proc {}
15
+ # end
16
+
17
+ # resolver = configuration.router.resolver.new(
18
+ # slices: Hanami.application.slices,
19
+ # inflector: Hanami.application.inflector # TODO: use container[:inflector]?
20
+ # )
21
+
22
+ # router = Hanami::Application::Router.new(
23
+ # routes: routes,
24
+ # resolver: resolver,
25
+ # **configuration.router.options,
26
+ # ) do
27
+ # use Hanami.application[:rack_monitor]
28
+
29
+ # Hanami.application.config.for_each_middleware do |m, *args, &block|
30
+ # use(m, *args, &block)
31
+ # end
32
+ # end
33
+
34
+ # register :router, router
35
+ # end
36
+ # end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/constants"
4
+
5
+ module Hanami
6
+ class Application
7
+ class Settings
8
+ # Default application settings store.
9
+ #
10
+ # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to load
11
+ # .env files and then loads settings from ENV. For a given `HANAMI_ENV`
12
+ # environment, the following `.env` files are looked up in the following order:
13
+ #
14
+ # - .env.{environment}.local
15
+ # - .env.local (except if the environment is `test`)
16
+ # - .env.{environment}
17
+ # - .env
18
+ #
19
+ # @since 2.0.0
20
+ # @api private
21
+ class DotenvStore
22
+ Undefined = Dry::Core::Constants::Undefined
23
+
24
+ attr_reader :store,
25
+ :hanami_env
26
+
27
+ def initialize(store: ENV, hanami_env: Hanami.env)
28
+ @store = store
29
+ @hanami_env = hanami_env
30
+ end
31
+
32
+ def fetch(name, default_value = Undefined, &block)
33
+ name = name.to_s.upcase
34
+ args = (default_value == Undefined) ? [name] : [name, default_value]
35
+
36
+ store.fetch(*args, &block)
37
+ end
38
+
39
+ def with_dotenv_loaded
40
+ require "dotenv"
41
+ Dotenv.load(*dotenv_files) if defined?(Dotenv)
42
+ self
43
+ rescue LoadError
44
+ self
45
+ end
46
+
47
+ private
48
+
49
+ def dotenv_files
50
+ [
51
+ ".env.#{hanami_env}.local",
52
+ (".env.local" unless hanami_env == :test),
53
+ ".env.#{hanami_env}",
54
+ ".env"
55
+ ].compact
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/constants"
5
+
6
+ module Hanami
7
+ class Application
8
+ # Application settings
9
+ #
10
+ # Users are expected to inherit from this class to define their application
11
+ # settings.
12
+ #
13
+ # @example
14
+ # # config/settings.rb
15
+ # # frozen_string_literal: true
16
+ #
17
+ # require "hanami/application/settings"
18
+ # require "my_app/types"
19
+ #
20
+ # module MyApp
21
+ # class Settings < Hanami::Application::Settings
22
+ # setting :database_url
23
+ # setting :feature_flag, default: false, constructor: Types::Params::Bool
24
+ # end
25
+ # end
26
+ #
27
+ # Settings are defined with
28
+ # [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you can
29
+ # take a look there to see the supported syntax.
30
+ #
31
+ # Users work with an instance of this class made available within the
32
+ # `settings` key in the container. The instance gets its settings populated
33
+ # from a configurable store, which defaults to
34
+ # {Hanami::Application::Settings::DotenvStore}.
35
+ #
36
+ # A different store can be set through the `settings_store` Hanami
37
+ # configuration option. All it needs to do is implementing a `#fetch` method
38
+ # with the same signature as `Hash#fetch`.
39
+ #
40
+ # @see Hanami::Application::Settings::DotenvStore
41
+ # @since 2.0.0
42
+ class Settings
43
+ # Exception for errors in the definition of settings.
44
+ #
45
+ # Its message collects all the individual errors that can be raised for
46
+ # each setting.
47
+ InvalidSettingsError = Class.new(StandardError) do
48
+ def initialize(errors)
49
+ @errors = errors
50
+ end
51
+
52
+ def to_s
53
+ <<~STR.strip
54
+ Could not initialize settings. The following settings were invalid:
55
+
56
+ #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
57
+ STR
58
+ end
59
+ end
60
+
61
+ # @api private
62
+ EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
63
+
64
+ include Dry::Configurable
65
+
66
+ # @api private
67
+ def initialize(store = EMPTY_STORE)
68
+ errors = config._settings.map(&:name).reduce({}) do |errs, name|
69
+ public_send("#{name}=", store.fetch(name) { Dry::Core::Constants::Undefined })
70
+ errs
71
+ rescue => e # rubocop:disable Style/RescueStandardError
72
+ errs.merge(name => e)
73
+ end
74
+
75
+ raise InvalidSettingsError, errors if errors.any?
76
+ end
77
+
78
+ private
79
+
80
+ def method_missing(name, *args, &block)
81
+ if config.respond_to?(name)
82
+ config.send(name, *args, &block)
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ def respond_to_missing?(name, _include_all = false)
89
+ config.respond_to?(name) || super
90
+ end
91
+ end
92
+ end
93
+ end