# frozen_string_literal: true

module Hanami
  module Providers
    class DB < Hanami::Provider::Source
      # @api public
      # @since 2.2.0
      class SQLAdapter < Adapter
        # @api public
        # @since 2.2.0
        setting :extensions, mutable: true

        # @api public
        # @since 2.2.0
        def extension(*extensions)
          self.extensions.concat(extensions).uniq!
        end

        # @api public
        # @since 2.2.0
        def extensions
          config.extensions ||= []
        end

        # @api private
        def configure_from_adapter(other_adapter)
          super

          return if skip_defaults?

          # As part of gateway configuration, every gateway will receive the "any adapter" here,
          # which is a plain `Adapter`, not an `SQLAdapter`. Its configuration will have been merged
          # by `super`, so no further work is required.
          return unless other_adapter.is_a?(self.class)

          extensions.concat(other_adapter.extensions).uniq! unless skip_defaults?(:extensions)
        end

        # @api private
        def configure_for_database(database_url)
          return if skip_defaults?

          configure_plugins
          configure_extensions(database_url)
        end

        # @api private
        private def configure_plugins
          return if skip_defaults?(:plugins)

          # Configure the plugin via a frozen proc, so it can be properly uniq'ed when configured
          # for multiple gateways. See `Hanami::Providers::DB::Config#each_plugin`.
          plugin(relations: :instrumentation, &INSTRUMENTATION_PLUGIN_CONFIG)

          plugin relations: :auto_restrictions
        end

        # @api private
        INSTRUMENTATION_PLUGIN_CONFIG = -> plugin {
          plugin.notifications = target["notifications"]
        }.freeze
        private_constant :INSTRUMENTATION_PLUGIN_CONFIG

        # @api private
        private def configure_extensions(database_url)
          return if skip_defaults?(:extensions)

          # Extensions for all SQL databases
          extension(
            :caller_logging,
            :error_sql,
            :sql_comments
          )

          # Extensions for specific databases
          if database_url.to_s.start_with?(%r{postgres(ql)*://})
            extension(
              :pg_array,
              :pg_enum,
              :pg_json,
              :pg_range
            )
          end
        end

        # @api private
        def gateway_options
          {extensions: extensions}
        end

        # @api public
        # @since 2.2.0
        def clear
          config.extensions = nil
          super
        end
      end
    end
  end
end

        private def skip_defaults?(setting_name = nil)
          @skip_defaults[setting_name]
        end


# frozen_string_literal: true

require "dry/configurable"
require "dry/core"

module Hanami
  module Providers
    class DB < Hanami::Provider::Source
      # @api public
      # @since 2.2.0
      class Gateway
        include Dry::Core::Constants
        include Dry::Configurable

        setting :database_url
        setting :adapter_name, default: :sql
        setting :adapter, mutable: true
        setting :connection_options, default: {}

        # @api public
        # @since 2.2.0
        def adapter(name = Undefined)
          return config.adapter if name.eql?(Undefined)

          if block_given?
            # If a block is given, explicitly configure the gateway's adapter
            config.adapter_name = name
            adapter = (config.adapter ||= Adapters.new_adapter(name))
            yield adapter
            adapter
          else
            # If an adapter name is given without a block, use the default adapter configured with
            # the same name
            config.adapter_name = adapter_name
          end
        end

        # @api public
        # @since 2.2.0
        def connection_options(**options)
          if options.any?
            config.connection_options.merge!(options)
          end

          config.connection_options
        end

        # @api public
        # @since 2.2.0
        def options
          {**connection_options, **config.adapter.gateway_options}
        end

        # @api private
        def configure_adapter(default_adapters)
          default_adapter = default_adapters.find(config.adapter_name)
          config.adapter ||= default_adapter.dup

          config.adapter.configure_from_adapter(default_adapter)

          config.adapter.configure_for_database(config.database_url)

          self
        end

        # @api private
        def cache_keys
          [config.database_url, config.connection_options, config.adapter.gateway_cache_keys]
        end

        private

        def method_missing(name, *args, &block)
          if config.respond_to?(name)
            config.public_send(name, *args, &block)
          else
            super
          end
        end

        def respond_to_missing?(name, _include_all = false)
          config.respond_to?(name) || super
        end
      end
    end
  end
end

adapter

        def self.new_adapter(name)
          ADAPTER_CLASSES[name].new
        end


# frozen_string_literal: true

module Hanami
  class Slice
    # @api private
    module Routing
      # Hanami app router endpoint resolver
      #
      # This resolves endpoints objects from a slice container using the strings passed to `to:` as
      # their container keys.
      #
      # @api private
      # @since 2.0.0
      class Resolver
        SLICE_ACTIONS_KEY_NAMESPACE = "actions"

        # @api private
        # @since 2.0.0
        def initialize(slice:)
          @slice = slice
        end

        # @api private
        # @since 2.0.0
        def find_slice(slice_name)
          slice.slices[slice_name]
        end

        # @api private
        # @since 2.0.0
        def to_slice(slice_name)
          self.class.new(slice: find_slice(slice_name))
        end

        # @api private
        # @since 2.0.0
        def call(_path, endpoint)
          endpoint =
            case endpoint
            when String
              resolve_slice_action(endpoint)
            when Class
              endpoint.respond_to?(:call) ? endpoint : endpoint.new
            else
              endpoint
            end

          unless endpoint.respond_to?(:call)
            raise Routes::NotCallableEndpointError.new(endpoint)
          end

          endpoint
        end

        private

        # @api private
        # @since 2.0.0
        attr_reader :slice

        # @api private
        # @since 2.0.0
        def resolve_slice_action(key)
          action_key = "#{SLICE_ACTIONS_KEY_NAMESPACE}.#{key}"

          ensure_action_in_slice(action_key)

          # Lazily resolve action from the slice to reduce router initialization time, and
          # circumvent endless loops from the action requiring access to router-related
          # concerns (which may not be fully loaded at the time of reading the routes)
          -> (*args) {
            action = slice.resolve(action_key) do
              raise Routes::MissingActionError.new(action_key, slice)
            end

            action.call(*args)
          }
        end

        def ensure_action_in_slice(key)
          return unless slice.booted?

          raise Routes::MissingActionError.new(key, slice) unless slice.key?(key)
        end
      end
    end
  end
end


def resolve_slice_action(key)
          action_key = "#{SLICE_ACTIONS_KEY_NAMESPACE}.#{key}"

          ensure_action_in_slice(action_key)

          # Lazily resolve action from the slice to reduce router initialization time, and
          # circumvent endless loops from the action requiring access to router-related
          # concerns (which may not be fully loaded at the time of reading the routes)
          -> (*args) {
            action = slice.resolve(action_key) do
              raise Routes::MissingActionError.new(action_key, slice)
            end

            action.call(*args)
          }
        end

        def ensure_action_in_slice(key)
          return unless slice.booted?

          raise Routes::MissingActionError.new(key, slice) unless slice.key?(key)
        end


# frozen_string_literal: true

require "hanami/router"
require "hanami/middleware"
require "hanami/middleware/app"
require "hanami/errors"

module Hanami
  class Slice
    module Routing
      # @since 2.0.0
      # @api private
      module Middleware
        # Wraps a rack app with a middleware stack
        #
        # We use this class to add middlewares to the rack application generated from
        # {Hanami::Slice::Router}.
        #
        # ```
        # stack = Hanami::Slice::Routing::Middleware::Stack.new
        # stack.use(Rack::ContentType, "text/html")
        # stack.to_rack_app(a_rack_app)
        # ```
        #
        # Middlewares can be mounted on specific paths:
        #
        # ```
        # stack.with("/api") do
        #   stack.use(Rack::ContentType, "application/json")
        # end
        # ```
        #
        # @see Hanami::Config#middleware
        #
        # @since 2.0.0
        # @api private
        class Stack
          include Enumerable

          # @since 2.0.0
          # @api private
          attr_reader :stack

          # Returns an array of Ruby namespaces from which to load middleware classes specified by
          # symbol names given to {#use}.
          #
          # Defaults to `[Hanami::Middleware]`.
          #
          # @return [Array<Object>]
          #
          # @api public
          # @since 2.0.0
          attr_reader :namespaces

          # @since 2.0.0
          # @api private
          def initialize
            @stack = Hash.new { |hash, key| hash[key] = [] }
            @namespaces = [Hanami::Middleware]
          end

          # @since 2.0.0
          # @api private
          def initialize_copy(source)
            super
            @stack = stack.dup
            @namespaces = namespaces.dup
          end

          # Adds a middleware to the stack.
          #
          # @example
          #   # Using a symbol name; adds Hanami::Middleware::BodyParser.new([:json])
          #   middleware.use :body_parser, :json
          #
          #   # Using a class name
          #   middleware.use MyMiddleware
          #
          #   # Adding a middleware before or after others
          #   middleware.use MyMiddleware, before: SomeMiddleware
          #   middleware.use MyMiddleware, after: OtherMiddleware
          #
          # @param spec [Symbol, Class] the middleware name or class name
          # @param args [Array, nil] Arguments to pass to the middleware's `.new` method
          # @param before [Class, nil] an optional (already added) middleware class to add the
          #   middleware before
          # @param after [Class, nil] an optional (already added) middleware class to add the
          #   middleware after
          #
          # @return [self]
          #
          # @api public
          # @since 2.0.0
          def use(spec, *args, path_prefix: ::Hanami::Router::DEFAULT_PREFIX, before: nil, after: nil, **kwargs, &blk)
            middleware = resolve_middleware_class(spec)
            item = [middleware, args, kwargs, blk]

            if before
              @stack[path_prefix].insert((idx = index_of(before, path_prefix)).zero? ? 0 : idx - 1, item)
            elsif after
              @stack[path_prefix].insert(index_of(after, path_prefix) + 1, item)
            else
              @stack[path_prefix].push(item)
            end

            self
          end

          # @since 2.0.0
          # @api private
          def update(other)
            other.stack.each do |path_prefix, items|
              stack[path_prefix].concat(items)
            end
            self
          end

          # @since 2.0.0
          # @api private
          def to_rack_app(app)
            unless Hanami.bundled?("rack")
              raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
            end

            mapping = to_hash
            return app if mapping.empty?

            Hanami::Middleware::App.new(app, mapping)
          end

          # @since 2.0.0
          # @api private
          def to_hash
            @stack.each_with_object({}) do |(path, _), result|
              result[path] = stack_for(path)
            end
          end

          # @since 2.0.0
          # @api private
          def empty?
            @stack.empty?
          end

          # @since 2.0.0
          # @api private
          def each(&blk)
            @stack.each(&blk)
          end

          # @since 2.0.0
          # @api private
          def mapped(builder, prefix, &blk)
            if prefix == ::Hanami::Router::DEFAULT_PREFIX
              builder.instance_eval(&blk)
            else
              builder.map(prefix, &blk)
            end
          end

          private

          # @since 2.0.0
          def index_of(middleware, path_prefix)
            @stack[path_prefix].index { |(m, *)| m.equal?(middleware) }
          end

          # @since 2.0.0
          # @api private
          def stack_for(current_path)
            @stack.each_with_object([]) do |(path, stack), result|
              next unless current_path.start_with?(path)

              result.push(stack)
            end.flatten(1)
          end

          # @since 2.0.0
          def resolve_middleware_class(spec)
            case spec
            when Symbol then load_middleware_class(spec)
            when Class, Module then spec
            else
              if spec.respond_to?(:call)
                spec
              else
                raise UnsupportedMiddlewareSpecError, spec
              end
            end
          end

          # @since 2.0.0
          def load_middleware_class(spec)
            begin
              require "hanami/middleware/#{spec}"
            rescue LoadError # rubocop:disable Lint/SuppressedException
            end

            # FIXME: Classify must use App inflector
            class_name = Hanami::Utils::String.classify(spec.to_s)
            namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }

            if namespace
              namespace.const_get(class_name)
            else
              raise(
                UnsupportedMiddlewareSpecError,
                "Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
              )
            end
          end
        end
      end
    end
  end
end


def resolve_middleware_class(spec)
            case spec
            when Symbol then load_middleware_class(spec)
            when Class, Module then spec
            else
              if spec.respond_to?(:call)
                spec
              else
                raise UnsupportedMiddlewareSpecError, spec
              end
            end
          end

          def load_middleware_class(spec)
            begin
              require "hanami/middleware/#{spec}"
            rescue LoadError # rubocop:disable Lint/SuppressedException
            end

            # FIXME: Classify must use App inflector
            class_name = Hanami::Utils::String.classify(spec.to_s)
            namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }

            if namespace
              namespace.const_get(class_name)
            else
              raise(
                UnsupportedMiddlewareSpecError,
                "Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
              )
            end
          end


def load_middleware_class(spec)
            begin
              require "hanami/middleware/#{spec}"
            rescue LoadError # rubocop:disable Lint/SuppressedException
            end

            # FIXME: Classify must use App inflector
            class_name = Hanami::Utils::String.classify(spec.to_s)
            namespace = namespaces.detect { |ns| ns.const_defined?(class_name) }

            if namespace
              namespace.const_get(class_name)
            else
              raise(
                UnsupportedMiddlewareSpecError,
                "Failed to find corresponding middleware class for `#{spec}` in #{namespaces.join(', ')}"
              )
            end
          end


# frozen_string_literal: true

module Hanami
  module Extensions
    module Operation
      # Extends operations to support the database.
      #
      # Add an initializer accepting a `rom:` dependency, which is supplied automatically from the
      # `"db.rom"` component in the operation's slice.
      #
      # @api private
      class SliceConfiguredDBOperation < Module
        attr_reader :slice

        def initialize(slice)
          super()
          @slice = slice
        end

        def extended(operation_class)
          require "dry/operation/extensions/rom"
          operation_class.include Dry::Operation::Extensions::ROM

          operation_class.include InstanceMethods
          operation_class.include SliceInstanceMethods.new(slice)
          define_new
        end

        def inspect
          "#<#{self.class.name}[#{slice.name}>"
        end

        private

        def define_new
          resolve_rom = method(:resolve_rom)

          define_method(:new) do |**kwargs|
            super(
              **kwargs,
              rom: kwargs.fetch(:rom) { resolve_rom.() },
            )
          end
        end

        def resolve_rom
          slice["db.rom"] if slice.key?("db.rom")
        end

        module InstanceMethods
          attr_reader :rom

          def initialize(rom:, **)
            super()
            @rom = rom
          end
        end

        class SliceInstanceMethods < Module
          attr_reader :slice

          def initialize(slice)
            super()
            @slice = slice
          end

          def included(operation_class)
            define_transaction
          end

          private

          def define_transaction
            slice = self.slice
            define_method(:transaction) do |**kwargs, &block|
              unless rom
                msg = <<~TEXT.strip
                  A configured db for #{slice} is required to run transactions.
                TEXT
                raise Hanami::ComponentLoadError, msg
              end

              super(**kwargs, &block)
            end
          end
        end
      end
    end
  end
end

class SliceConfiguredDBOperation < < Module


# frozen_string_literal: true

require "hanami/db"

module Hanami
  module Extensions
    # @api private
    # @since 2.2.0
    module DB
      # @api private
      # @since 2.2.0
      module Repo
        def self.included(repo_class)
          super

          repo_class.extend(Hanami::SliceConfigurable)
          repo_class.extend(ClassMethods)
        end

        # @api private
        # @since 2.2.0
        module ClassMethods
          def configure_for_slice(slice)
            extend SliceConfiguredRepo.new(slice)
          end
        end
      end

      # @api private
      # @since 2.2.0
      class SliceConfiguredRepo < Module
        attr_reader :slice

        def initialize(slice)
          super()
          @slice = slice
        end

        def extended(repo_class)
          define_inherited
          configure_repo(repo_class)
          define_new
        end

        def inspect
          "#<#{self.class.name}[#{slice.name}]>"
        end

        private

        def define_inherited
          root_for_repo_class = method(:root_for_repo_class)

          define_method(:inherited) do |subclass|
            super(subclass)

            unless subclass.root
              root = root_for_repo_class.(subclass)
              subclass.root root if root
            end
          end
        end

        def configure_repo(repo_class)
          repo_class.struct_namespace struct_namespace
        end

        def define_new
          resolve_rom = method(:resolve_rom)

          define_method(:new) do |**kwargs|
            container = kwargs.delete(:container) || resolve_rom.()
            super(container: container, **kwargs)
          end
        end

        def resolve_rom
          slice["db.rom"]
        end

        REPO_CLASS_NAME_REGEX = /^(?<name>.+)_(repo|repository)$/

        def root_for_repo_class(repo_class)
          repo_class_name = slice.inflector.demodulize(repo_class)
            .then { slice.inflector.underscore(_1) }

          repo_class_match = repo_class_name.match(REPO_CLASS_NAME_REGEX)
          return unless repo_class_match

          repo_class_match[:name]
            .then { slice.inflector.pluralize(_1) }
            .then(&:to_sym)
        end

        def struct_namespace
          @struct_namespace ||=
            if slice.namespace.const_defined?(:Structs)
              slice.namespace.const_get(:Structs)
            else
              slice.namespace.const_set(:Structs, Module.new)
            end
        end
      end
    end
  end
end

Hanami::DB::Repo.include(Hanami::Extensions::DB::Repo)


# frozen_string_literal: true

module Hanami
  module Extensions
    module View
      # Provides slice-specific configuration and behavior for any view class defined within a
      # slice's module namespace.
      #
      # @api public
      # @since 2.1.0
      class SliceConfiguredView < Module
        TEMPLATES_DIR = "templates"
        VIEWS_DIR = "views"
        PARTS_DIR = "parts"
        SCOPES_DIR = "scopes"

        attr_reader :slice

        # @api private
        # @since 2.1.0
        def initialize(slice)
          super()
          @slice = slice
        end

        # @api private
        # @since 2.1.0
        def extended(view_class)
          load_app_view
          configure_view(view_class)
          define_inherited
        end

        # @return [String]
        #
        # @api public
        # @since 2.1.0
        def inspect
          "#<#{self.class.name}[#{slice.name}]>"
        end

        private

        # If the given view doesn't inherit from the app view, attempt to load it anyway, since
        # requiring the app view is necessary for _its_ `SliceConfiguredView` hook to execute and
        # define the app-level part and scope classes that we refer to here.
        def load_app_view
          return if app?

          begin
            slice.app.namespace.const_get(:View, false)
          rescue NameError => exception
            raise unless exception.name == :View
          end
        end

        # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
        def configure_view(view_class)
          view_class.settings.each do |setting|
            next unless slice.config.views.respond_to?(setting.name)

            # Configure the view from config on the slice, _unless it has already been configured by
            # a parent slice_, and re-configuring it for this slice would make no change.
            #
            # In the case of most slices, its views config is likely to be the same as its parent
            # (since each slice copies its `config` from its parent), and if we re-apply the config
            # here, then it may possibly overwrite config customisations explicitly made in parent
            # view classes.
            #
            # For example, given an app-level base view class, with custom config:
            #
            #   module MyApp
            #     class View < Hanami::View
            #       config.layout = "custom_layout"
            #     end
            #   end
            #
            # And then a view in a slice inheriting from it:
            #
            #   module MySlice
            #     module Views
            #       class SomeView < MyApp::View
            #       end
            #     end
            #   end
            #
            # In this case, `SliceConfiguredView` will be extended two times:
            #
            # 1. When `MyApp::View` is defined
            # 2. Again when `MySlice::Views::SomeView` is defined
            #
            # If we blindly re-configure all view settings each time `SliceConfiguredView` is
            # extended, then at the point of (2) above, we'd end up overwriting the custom
            # `config.layout` explicitly configured in the `MyApp::View` base class, leaving
            # `MySlice::Views::SomeView` with `config.layout` of `"app"` (the default as specified
            # at `Hanami.app.config.views.layout`), and not the `"custom_layout"` value configured
            # in its immediate superclass.
            #
            # This would be surprising behavior, and we want to avoid it.
            slice_value = slice.config.views.public_send(setting.name)
            parent_value = slice.parent.config.views.public_send(setting.name) if slice.parent

            next if slice.parent && slice_value == parent_value

            view_class.config.public_send(
              :"#{setting.name}=",
              setting.mutable? ? slice_value.dup : slice_value
            )
          end

          view_class.config.inflector = inflector

          # Configure the paths for this view if:
          #   - We are the app, and a user hasn't provided custom `paths` (in this case, we need to
          #     set the defaults)
          #   - We are a slice, and the view's inherited `paths` is identical to the parent's config
          #     (which would result in the view in a slice erroneously trying to find templates in
          #     the app)
          if view_class.config.paths.empty? ||
             (slice.parent && view_class.config.paths.map(&:dir) == [templates_path(slice.parent)])
            view_class.config.paths = templates_path(slice)
          end

          view_class.config.template = template_name(view_class)
          view_class.config.default_context = Extensions::View::Context.context_class(slice).new

          view_class.config.part_class = part_class
          view_class.config.scope_class = scope_class

          if (part_namespace = namespace_from_path("#{VIEWS_DIR}/#{PARTS_DIR}"))
            view_class.config.part_namespace = part_namespace
          end
          if (scope_namespace = namespace_from_path("#{VIEWS_DIR}/#{SCOPES_DIR}"))
            view_class.config.scope_namespace = scope_namespace
          end
        end
        # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

        def define_inherited
          template_name = method(:template_name)

          define_method(:inherited) do |subclass|
            super(subclass)
            subclass.config.template = template_name.(subclass)
          end
        end

        def templates_path(slice)
          if slice.app.equal?(slice)
            slice.root.join(APP_DIR, TEMPLATES_DIR)
          else
            slice.root.join(TEMPLATES_DIR)
          end
        end

        def namespace_from_path(path)
          path = "#{slice.slice_name.path}/#{path}"

          begin
            require path
          rescue LoadError => exception
            raise exception unless exception.path == path
          end

          begin
            inflector.constantize(inflector.camelize(path))
          rescue NameError # rubocop: disable Lint/SuppressedException
          end
        end

        def template_name(view_class)
          inflector
            .underscore(view_class.name)
            .sub(/^#{slice.slice_name.path}\//, "")
            .sub(/^#{VIEWS_DIR}\//, "")
        end

        def inflector
          slice.inflector
        end

        def part_class
          @part_class ||=
            if views_namespace.const_defined?(:Part)
              views_namespace.const_get(:Part)
            else
              views_namespace.const_set(:Part, Class.new(part_superclass).tap { |klass|
                # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
                # called via `.inherited`, because the class is anonymous at this point
                klass.configure_for_slice(slice)
              })
            end
        end

        def part_superclass
          return Hanami::View::Part if app?

          begin
            inflector.constantize(inflector.camelize("#{slice.app.slice_name.name}/views/part"))
          rescue NameError
            Hanami::View::Part
          end
        end

        def scope_class
          @scope_class ||=
            if views_namespace.const_defined?(:Scope)
              views_namespace.const_get(:Scope)
            else
              views_namespace.const_set(:Scope, Class.new(scope_superclass).tap { |klass|
                # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
                # called via `.inherited`, since the class is anonymous at this point
                klass.configure_for_slice(slice)
              })
            end
        end

        def scope_superclass
          return Hanami::View::Scope if app?

          begin
            inflector.constantize(inflector.camelize("#{slice.app.slice_name.name}/views/scope"))
          rescue NameError
            Hanami::View::Scope
          end
        end

        def views_namespace
          @slice_views_namespace ||=
            if slice.namespace.const_defined?(:Views)
              slice.namespace.const_get(:Views)
            else
              slice.namespace.const_set(:Views, Module.new)
            end
        end

        def app?
          slice.app == slice
        end
      end
    end
  end
end

config

!(view_class.config.paths.empty? ||
             (slice.parent && view_class.config.paths.map(&:dir) == [templates_path(slice.parent)]))

          def context_class(slice)
            views_namespace = views_namespace(slice)

            if views_namespace.const_defined?(:Context)
              return views_namespace.const_get(:Context)
            end

            views_namespace.const_set(:Context, Class.new(context_superclass(slice)).tap { |klass|
              klass.configure_for_slice(slice)
            })
          end


def namespace_from_path(path)
          path = "#{slice.slice_name.path}/#{path}"

          begin
            require path
          rescue LoadError => exception
            raise exception unless exception.path == path
          end

          begin
            inflector.constantize(inflector.camelize(path))
          rescue NameError # rubocop: disable Lint/SuppressedException
          end
        end


def part_class
          @part_class ||=
            if views_namespace.const_defined?(:Part)
              views_namespace.const_get(:Part)
            else
              views_namespace.const_set(:Part, Class.new(part_superclass).tap { |klass|
                # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
                # called via `.inherited`, because the class is anonymous at this point
                klass.configure_for_slice(slice)
              })
            end
        end


def scope_class
          @scope_class ||=
            if views_namespace.const_defined?(:Scope)
              views_namespace.const_get(:Scope)
            else
              views_namespace.const_set(:Scope, Class.new(scope_superclass).tap { |klass|
                # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
                # called via `.inherited`, since the class is anonymous at this point
                klass.configure_for_slice(slice)
              })
            end
        end


# frozen_string_literal: true

module Hanami
  module Extensions
    module View
      # Provides slice-specific configuration and behavior for any view part class defined within a
      # slice's module namespace.
      #
      # @api public
      # @since 2.1.0
      class SliceConfiguredPart < Module
        attr_reader :slice

        # @api private
        # @since 2.1.0
        def initialize(slice)
          super()
          @slice = slice
        end

        # @api private
        # @since 2.1.0
        def extended(klass)
          define_new
        end

        # @return [String]
        #
        # @api public
        # @since 2.1.0
        def inspect
          "#<#{self.class.name}[#{slice.name}]>"
        end

        private

        # Defines a `.new` method on the part class that provides a default `rendering:` argument of
        # a rendering coming from a view configured for the slice. This means that any part can be
        # initialized standalone (with a `value:` only) and still have access to all the integrated
        # view facilities from the slice, such as helpers. This is helpful when unit testing parts.
        #
        # @example
        #   module MyApp::Views::Parts
        #     class Post < MyApp::View::Part
        #       def title_tag
        #         helpers.h1(value.title)
        #       end
        #     end
        #   end
        #
        #   # Useful when unit testing parts
        #   part = MyApp::Views::Parts::Post.new(value: hello_world_post)
        #   part.title_tag # => "
Hello world
"
        def define_new
          slice = self.slice

          define_method(:new) do |**args|
            return super(**args) if args.key?(:rendering)

            slice_rendering = Class.new(Hanami::View)
              .configure_for_slice(slice)
              .new
              .rendering

            super(rendering: slice_rendering, **args)
          end
        end
      end
    end
  end
end


# frozen_string_literal: true

require_relative "../../errors"
require_relative "slice_configured_context"

module Hanami
  module Extensions
    module View
      # View context for views in Hanami apps.
      #
      # @api public
      # @since 2.1.0
      module Context
        class << self
          # Returns a context class for the given slice. If a context class is not defined, defines
          # a class named `Views::Context` within the slice's namespace.
          #
          # @api private
          # @since 2.1.0
          def context_class(slice)
            views_namespace = views_namespace(slice)

            if views_namespace.const_defined?(:Context)
              return views_namespace.const_get(:Context)
            end

            views_namespace.const_set(:Context, Class.new(context_superclass(slice)).tap { |klass|
              klass.configure_for_slice(slice)
            })
          end

          private

          # @api private
          # @since 2.1.0
          def context_superclass(slice)
            return Hanami::View::Context if Hanami.app.equal?(slice)

            begin
              slice.inflector.constantize(
                slice.inflector.camelize("#{slice.app.slice_name.name}/views/context")
              )
            rescue NameError => exception
              raise exception unless %i[Views Context].include?(exception.name)

              Hanami::View::Context
            end
          end

          # @api private
          # @since 2.1.0
          def views_namespace(slice)
            # TODO: this could be moved into the top-level Extensions::View
            if slice.namespace.const_defined?(:Views)
              slice.namespace.const_get(:Views)
            else
              slice.namespace.const_set(:Views, Module.new)
            end
          end
        end

        # @api private
        # @since 2.1.0
        module ClassExtension
          def self.included(context_class)
            super

            context_class.extend(Hanami::SliceConfigurable)
            context_class.extend(ClassMethods)
            context_class.prepend(InstanceMethods)
          end

          # @api private
          # @since 2.1.0
          module ClassMethods
            # @api private
            # @since 2.1.0
            def configure_for_slice(slice)
              extend SliceConfiguredContext.new(slice)
            end
          end

          # @api public
          # @since 2.1.0
          module InstanceMethods
            # Returns the app's inflector.
            #
            # @return [Dry::Inflector] the inflector
            #
            # @api public
            # @since 2.1.0
            attr_reader :inflector

            # @see SliceConfiguredContext#define_new
            #
            # @api private
            # @since 2.1.0
            def initialize(
              inflector: nil,
              routes: nil,
              assets: nil,
              request: nil,
              **args
            )
              @inflector = inflector
              @routes = routes
              @assets = assets
              @request = request

              @content_for = {}

              super(**args)
            end

            # @api private
            # @since 2.1.0
            def initialize_copy(source)
              # The standard implementation of initialize_copy will make shallow copies of all
              # instance variables from the source. This is fine for most of our ivars.
              super

              # Dup any objects that will be mutated over a given rendering to ensure no leakage of
              # state across distinct view renderings.
              @content_for = source.instance_variable_get(:@content_for).dup
            end

            # Returns the app's assets.
            #
            # @return [Hanami::Assets] the assets
            #
            # @raise [Hanami::ComponentLoadError] if the hanami-assets gem is not bundled
            #
            # @api public
            # @since 2.1.0
            def assets
              unless @assets
                msg =
                  if Hanami.bundled?("hanami-assets")
                    "Have you put files into your assets directory?"
                  else
                    "The hanami-assets gem is required to access assets."
                  end

                raise Hanami::ComponentLoadError, "Assets not available. #{msg}"
              end

              @assets
            end

            # Returns the current request, if the view is rendered from within an action.
            #
            # @return [Hanami::Action::Request] the request
            #
            # @raise [Hanami::ComponentLoadError] if the view is not rendered from within a request
            #
            # @api public
            # @since 2.1.0
            def request
              unless @request
                raise Hanami::ComponentLoadError, <<~STR
                  Request not available. Only views rendered from Hanami::Action instances have a request.
                STR
              end

              @request
            end

            # Returns true if the view is rendered from within an action and a request is available.
            #
            # @return [Boolean]
            #
            # @api public
            # @since 2.3.0
            def request?
              !!@request
            end

            # Returns the app's routes helper.
            #
            # @return [Hanami::Slice::RoutesHelper] the routes helper
            #
            # @raise [Hanami::ComponentLoadError] if the hanami-router gem is not bundled or routes
            #   are not defined
            #
            # @api public
            # @since 2.1.0
            def routes
              unless @routes
                raise Hanami::ComponentLoadError, "the hanami-router gem is required to access routes"
              end

              @routes
            end

            # @overload content_for(key, value = nil, &block)
            #   Stores a string or block of template markup for later use.
            #
            #   @param key [Symbol] the content key, for later retrieval
            #   @param value [String, nil] the content, if no block is given
            #
            #   @return [String] the content
            #
            #   @example
            #     content_for(:page_title, "Hello world")
            #
            #   @example In a template
            #     <%= content_for :page_title do %>
            #
Hello world

            #     <% end %>
            #
            # @overload content_for(key)
            #   Returns the previously stored content for the given key.
            #
            #   @param key [Symbol] the content key
            #
            #   @return [String, nil] the content, or nil if no content previously stored with the
            #     key
            #
            # @api public
            # @since 2.1.0
            def content_for(key, value = nil)
              if block_given?
                @content_for[key] = yield
                nil
              elsif value
                @content_for[key] = value
                nil
              else
                @content_for[key]
              end
            end

            # Returns the current request's CSRF token.
            #
            # @return [String] the token
            #
            # @raise [Hanami::ComponentLoadError] if the view is not rendered from within a request
            # @raise [Hanami::Action::MissingSessionError] if sessions are not enabled
            #
            # @api public
            # @since 2.1.0
            def csrf_token
              request.session[Hanami::Action::CSRFProtection::CSRF_TOKEN]
            end

            # Returns the session for the current request.
            #
            # @return [Rack::Session::Abstract::SessionHash] the session hash
            #
            # @raise [Hanami::ComponentLoadError] if the view is not rendered from within a request
            # @raise [Hanami::Action::MissingSessionError] if sessions are not enabled
            #
            # @api public
            # @since 2.1.0
            def session
              request.session
            end

            # Returns the flash hash for the current request.
            #
            # @return []
            #
            # @raise [Hanami::ComponentLoadError] if the view is not rendered from within a request
            # @raise [Hanami::Action::MissingSessionError] if sessions are not enabled
            #
            # @api public
            # @since 2.1.0
            def flash
              request.flash
            end
          end
        end
      end
    end
  end
end

Hanami::View::Context.include(Hanami::Extensions::View::Context::ClassExtension)


def initialize(
              inflector: nil,
              routes: nil,
              assets: nil,
              request: nil,
              **args
            )
              @inflector = inflector
              @routes = routes
              @assets = assets
              @request = request

              @content_for = {}

              super(**args)
            end


def assets
              unless @assets
                msg =
                  if Hanami.bundled?("hanami-assets")
                    "Have you put files into your assets directory?"
                  else
                    "The hanami-assets gem is required to access assets."
                  end

                raise Hanami::ComponentLoadError, "Assets not available. #{msg}"
              end

              @assets
            end

assets

bundled?

  def self.bundled?(gem_name)
    @_mutex.synchronize do
      @_bundled[gem_name] ||= begin
        gem(gem_name)
      rescue Gem::LoadError
        false
      end
    end
  end


def content_for(key, value = nil)
              if block_given?
                @content_for[key] = yield
                nil
              elsif value
                @content_for[key] = value
                nil
              else
                @content_for[key]
              end
            end


# frozen_string_literal: true

module Hanami
  module Extensions
    module View
      # Provides slice-specific configuration and behavior for any view context class defined within
      # a slice's module namespace.
      #
      # @api public
      # @since 2.1.0
      class SliceConfiguredContext < Module
        attr_reader :slice

        # @api private
        # @since 2.1.0
        def initialize(slice)
          super()
          @slice = slice
        end

        # @api private
        # @since 2.1.0
        def extended(_context_class)
          define_new
        end

        # @api public
        # @since 2.1.0
        def inspect
          "#<#{self.class.name}[#{slice.name}]>"
        end

        private

        # Defines a {.new} method on the context class that resolves key components from the app
        # container and provides them to {#initialize} as injected dependencies.
        #
        # This includes the following app components:
        #   - the configured inflector as `inflector`
        #   - "routes" from the app container as `routes`
        #   - "assets" from the app container as `assets`
        def define_new
          inflector = slice.inflector
          resolve_routes = method(:resolve_routes)
          resolve_assets = method(:resolve_assets)

          define_method :new do |**kwargs|
            kwargs[:inflector] ||= inflector
            kwargs[:routes] ||= resolve_routes.()
            kwargs[:assets] ||= resolve_assets.()

            super(**kwargs)
          end
        end

        def resolve_routes
          slice["routes"] if slice.key?("routes")
        end

        def resolve_assets
          slice["assets"] if slice.key?("assets")
        end
      end
    end
  end
end


# frozen_string_literal: true

module Hanami
  module Extensions
    module Action
      # Provides slice-specific configuration and behavior for any action class defined
      # within a slice's module namespace.
      #
      # @api private
      # @since 2.0.0
      class SliceConfiguredAction < Module
        attr_reader :slice

        def initialize(slice)
          super()
          @slice = slice
        end

        def extended(action_class)
          configure_action(action_class)
          extend_behavior(action_class)
          define_new
        end

        def inspect
          "#<#{self.class.name}[#{slice.name}]>"
        end

        private

        # @see Hanami::Extensions::Action::InstanceMethods#initialize
        def define_new
          resolve_view = method(:resolve_paired_view)
          view_context_class = method(:view_context_class)
          resolve_routes = method(:resolve_routes)
          resolve_rack_monitor = method(:resolve_rack_monitor)

          define_method(:new) do |**kwargs|
            super(
              view: kwargs.fetch(:view) { resolve_view.(self) },
              view_context_class: kwargs.fetch(:view_context_class) { view_context_class.() },
              routes: kwargs.fetch(:routes) { resolve_routes.() },
              rack_monitor: kwargs.fetch(:rack_monitor) { resolve_rack_monitor.() },
              **kwargs,
            )
          end
        end

        def configure_action(action_class)
          action_class.settings.each do |setting|
            # Configure the action from config on the slice, _unless it has already been configured
            # by a parent slice_, and re-configuring it for this slice would make no change.
            #
            # In the case of most slices, its actions config is likely to be the same as its parent
            # (since each slice copies its `config` from its parent), and if we re-apply the config
            # here, then it may possibly overwrite config customisations explicitly made in parent
            # action classes.
            #
            # For example, given an app-level base action class, with custom config:
            #
            #   module MyApp
            #     class Action < Hanami::Action
            #       config.format :json
            #     end
            #   end
            #
            # And then an action in a slice inheriting from it:
            #
            #   module MySlice
            #     module Actions
            #       class SomeAction < MyApp::Action
            #       end
            #     end
            #   end
            #
            # In this case, `SliceConfiguredAction` will be extended two times:
            #
            # 1. When `MyApp::Action` is defined
            # 2. Again when `MySlice::Actions::SomeAction` is defined
            #
            # If we blindly re-configure all action settings each time `SliceConfiguredAction` is
            # extended, then at the point of (2) above, we'd end up overwriting the custom
            # `config.default_response_format` explicitly configured in the `MyApp::Action` base
            # class, leaving `MySlice::Actions::SomeAction` with `config.default_response_format` of
            # `:html` (the default at `Hanami.app.config.actions.default_response_format`), and not
            # the `:json` value configured in its immediate superclass.
            #
            # This would be surprising behavior, and we want to avoid it.
            slice_value = slice.config.actions.public_send(setting.name)
            parent_value = slice.parent.config.actions.public_send(setting.name) if slice.parent

            next if slice.parent && slice_value == parent_value

            action_class.config.public_send(
              :"#{setting.name}=",
              setting.mutable? ? slice_value.dup : slice_value
            )
          end
        end

        def extend_behavior(action_class)
          if actions_config.sessions.enabled?
            require "hanami/action/session"
            action_class.include Hanami::Action::Session
          end

          if actions_config.csrf_protection
            require "hanami/action/csrf_protection"
            action_class.include Hanami::Action::CSRFProtection
          end

          if actions_config.cookies.enabled?
            require "hanami/action/cookies"
            action_class.include Hanami::Action::Cookies
          end
        end

        def resolve_paired_view(action_class)
          view_identifiers = actions_config.view_name_inferrer.call(
            action_class_name: action_class.name,
            slice: slice
          )

          view_identifiers.each do |identifier|
            return slice[identifier] if slice.key?(identifier)
          end

          nil
        end

        def view_context_class
          if Hanami.bundled?("hanami-view")
            return Extensions::View::Context.context_class(slice)
          end

          # If hanami-view isn't bundled, try and find a possible third party context class with the
          # same `Views::Context` name (but don't fall back to automatically defining one).
          if slice.namespace.const_defined?(:Views)
            views_namespace = slice.namespace.const_get(:Views)

            if views_namespace.const_defined?(:Context)
              views_namespace.const_get(:Context)
            end
          end
        end

        def resolve_routes
          slice.app["routes"] if slice.app.key?("routes")
        end

        def resolve_rack_monitor
          slice.app["rack.monitor"] if slice.app.key?("rack.monitor")
        end

        def actions_config
          slice.config.actions
        end
      end
    end
  end
end


def configure_action(action_class)
          action_class.settings.each do |setting|
            # Configure the action from config on the slice, _unless it has already been configured
            # by a parent slice_, and re-configuring it for this slice would make no change.
            #
            # In the case of most slices, its actions config is likely to be the same as its parent
            # (since each slice copies its `config` from its parent), and if we re-apply the config
            # here, then it may possibly overwrite config customisations explicitly made in parent
            # action classes.
            #
            # For example, given an app-level base action class, with custom config:
            #
            #   module MyApp
            #     class Action < Hanami::Action
            #       config.format :json
            #     end
            #   end
            #
            # And then an action in a slice inheriting from it:
            #
            #   module MySlice
            #     module Actions
            #       class SomeAction < MyApp::Action
            #       end
            #     end
            #   end
            #
            # In this case, `SliceConfiguredAction` will be extended two times:
            #
            # 1. When `MyApp::Action` is defined
            # 2. Again when `MySlice::Actions::SomeAction` is defined
            #
            # If we blindly re-configure all action settings each time `SliceConfiguredAction` is
            # extended, then at the point of (2) above, we'd end up overwriting the custom
            # `config.default_response_format` explicitly configured in the `MyApp::Action` base
            # class, leaving `MySlice::Actions::SomeAction` with `config.default_response_format` of
            # `:html` (the default at `Hanami.app.config.actions.default_response_format`), and not
            # the `:json` value configured in its immediate superclass.
            #
            # This would be surprising behavior, and we want to avoid it.
            slice_value = slice.config.actions.public_send(setting.name)
            parent_value = slice.parent.config.actions.public_send(setting.name) if slice.parent

            next if slice.parent && slice_value == parent_value

            action_class.config.public_send(
              :"#{setting.name}=",
              setting.mutable? ? slice_value.dup : slice_value
            )
          end
        end


def extend_behavior(action_class)
          if actions_config.sessions.enabled?
            require "hanami/action/session"
            action_class.include Hanami::Action::Session
          end

          if actions_config.csrf_protection
            require "hanami/action/csrf_protection"
            action_class.include Hanami::Action::CSRFProtection
          end

          if actions_config.cookies.enabled?
            require "hanami/action/cookies"
            action_class.include Hanami::Action::Cookies
          end
        end


def resolve_paired_view(action_class)
          view_identifiers = actions_config.view_name_inferrer.call(
            action_class_name: action_class.name,
            slice: slice
          )

          view_identifiers.each do |identifier|
            return slice[identifier] if slice.key?(identifier)
          end

          nil
        end


def view_context_class
          if Hanami.bundled?("hanami-view")
            return Extensions::View::Context.context_class(slice)
          end

          # If hanami-view isn't bundled, try and find a possible third party context class with the
          # same `Views::Context` name (but don't fall back to automatically defining one).
          if slice.namespace.const_defined?(:Views)
            views_namespace = slice.namespace.const_get(:Views)

            if views_namespace.const_defined?(:Context)
              views_namespace.const_get(:Context)
            end
          end
        end