# frozen_string_literal: true

require "hanami/view"

module Hanami
  module Helpers
    # Helper methods for generating HTML forms.
    #
    # These helpers will be automatically available in your view templates, part classes and scope
    # classes.
    #
    # This module provides one primary method: {#form_for}, yielding an HTML form builder. This
    # integrates with request params and template locals to populate the form with appropriate
    # values.
    #
    # @api public
    # @since 2.1.0
    module FormHelper
      require_relative "form_helper/form_builder"

      # Default HTTP method for form
      #
      # @since 2.1.0
      # @api private
      DEFAULT_METHOD = "POST"

      # Default charset
      #
      # @since 2.1.0
      # @api private
      DEFAULT_CHARSET = "utf-8"

      # CSRF Token session key
      #
      # This name of this key is shared with the hanami and hanami-action gems.
      #
      # @since 2.1.0
      # @api private
      CSRF_TOKEN = :_csrf_token

      include Hanami::View::Helpers::TagHelper

      # Yields a form builder for constructing an HTML form and returns the resulting form string.
      #
      # See {FormHelper::FormBuilder} for the methods for building the form's fields.
      #
      # @overload form_for(base_name, url, values: _form_for_values, params: _form_for_params, **attributes)
      #   Builds the form using the given base name for all fields.
      #
      #   @param base_name [String] the base
      #   @param url [String] the URL for submitting the form
      #   @param values [Hash] values to be used for populating form field values; optional,
      #     defaults to the template's locals or to a part's `{name => self}`
      #   @param params [Hash] request param values to be used for populating form field values;
      #     these are used in preference over the `values`; optional, defaults to the current
      #     request's params
      #   @param attributes [Hash] the HTML attributes for the form tag
      #   @yieldparam [FormHelper::FormBuilder] f the form builder
      #
      # @overload form_for(url, values: _form_for_values, params: _form_for_params, **attributes)
      #   @param url [String] the URL for submitting the form
      #   @param values [Hash] values to be used for populating form field values; optional,
      #     defaults to the template's locals or to a part's `{name => self}`
      #   @param params [Hash] request param values to be used for populating form field values;
      #     these are used in preference over the `values`; optional, defaults to the current
      #     request's params
      #   @param attributes [Hash] the HTML attributes for the form tag
      #   @yieldparam [FormHelper::FormBuilder] f the form builder
      #
      # @return [String] the form HTML
      #
      # @see FormHelper
      # @see FormHelper::FormBuilder
      #
      # @example Basic usage
      #   <%= form_for("book", "/books", class: "form-horizontal") do |f| %>
      #

      #       <%= f.label "title" %>
      #       <%= f.text_field "title", class: "form-control" %>
      #

      #
      #     <%= f.submit "Create" %>
      #   <% end %>
      #
      #   =>
      #   <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
      #     <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
      #

      #       Title
      #       <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
      #

      #
      #     <button type="submit">Create</button>
      #   </form>
      #
      # @example Without base name
      #
      #   <%= form_for("/books", class: "form-horizontal") do |f| %>
      #

      #       <%= f.label "books.title" %>
      #       <%= f.text_field "books.title", class: "form-control" %>
      #

      #
      #     <%= f.submit "Create" %>
      #   <% end %>
      #
      #   =>
      #   <form action="/books" method="POST" accept-charset="utf-8" class="form-horizontal">
      #     <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
      #

      #       Title
      #       <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
      #

      #
      #     <button type="submit">Create</button>
      #   </form>
      #
      # @example Method override
      #   <%= form_for("/books/123", method: :put) do |f|
      #     <%= f.text_field "book.title" %>
      #     <%= f.submit "Update" %>
      #   <% end %>
      #
      #   =>
      #   <form action="/books/123" accept-charset="utf-8" method="POST">
      #     <input type="hidden" name="_method" value="PUT">
      #     <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
      #     <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
      #
      #     <button type="submit">Update</button>
      #   </form>
      #
      # @example Overriding values
      #   <%= form_for("/songs", values: {song: {title: "Envision"}}) do |f| %>
      #     <%= f.text_field "song.title" %>
      #     <%= f.submit "Create" %>
      #   <%= end %>
      #
      #   =>
      #   <form action="/songs" accept-charset="utf-8" method="POST">
      #     <input type="hidden" name="_csrf_token" value="920cd5bfaecc6e58368950e790f2f7b4e5561eeeab230aa1b7de1b1f40ea7d5d">
      #     <input type="text" name="song[title]" id="song-title" value="Envision">
      #
      #     <button type="submit">Create</button>
      #   </form>
      #
      # @api public
      # @since 2.1.0
      def form_for(base_name, url = nil, values: _form_for_values, params: _form_for_params, **attributes)
        url, base_name = base_name, nil if url.nil?

        values = Values.new(values: values, params: params, csrf_token: _form_csrf_token)

        builder = FormBuilder.new(
          base_name: base_name,
          values: values,
          inflector: _context.inflector,
          form_attributes: attributes
        )

        content = (block_given? ? yield(builder) : "").html_safe

        builder.call(content, action: url, **attributes)
      end

      # Returns CSRF meta tags for use via unobtrusive JavaScript (UJS) libraries.
      #
      # @return [String, nil] the tags, if a CSRF token is available, or nil
      #
      # @example
      #   csrf_meta_tags
      #
      #   =>
      #   <meta name="csrf-param" content="_csrf_token">
      #   <meta name="csrf-token" content="4a038be85b7603c406dcbfad4b9cdf91ec6ca138ed6441163a07bb0fdfbe25b5">
      #
      # @api public
      # @since 2.1.0
      def csrf_meta_tags
        return unless (token = _form_csrf_token)

        tag.meta(name: "csrf-param", content: CSRF_TOKEN) +
          tag.meta(name: "csrf-token", content: token)
      end

      # @api private
      # @since 2.1.0
      def _form_for_values
        if respond_to?(:_locals) # Scope
          _locals
        elsif respond_to?(:_name) # Part
          {_name => self}
        else
          {}
        end
      end

      # @api private
      # @since 2.1.0
      def _form_for_params
        _context.request.params
      end

      # @since 2.1.0
      # @api private
      def _form_csrf_token
        return unless _context.request.session_enabled?

        _context.csrf_token
      end
    end
  end
end


# frozen_string_literal: true

require "uri"
require "hanami/view"
require_relative "../constants"

module Hanami
  module Helpers
    # HTML assets helpers
    #
    # Inject these helpers in a view
    #
    # @since 2.1.0
    #
    # @see http://www.rubydoc.info/gems/hanami-helpers/Hanami/Helpers/HtmlHelper
    module AssetsHelper
      # @since 2.1.0
      # @api private
      NEW_LINE_SEPARATOR = "\n"

      # @since 2.1.0
      # @api private
      WILDCARD_EXT = ".*"

      # @since 2.1.0
      # @api private
      JAVASCRIPT_EXT = ".js"

      # @since 2.1.0
      # @api private
      STYLESHEET_EXT = ".css"

      # @since 2.1.0
      # @api private
      JAVASCRIPT_MIME_TYPE = "text/javascript"

      # @since 2.1.0
      # @api private
      STYLESHEET_MIME_TYPE = "text/css"

      # @since 2.1.0
      # @api private
      FAVICON_MIME_TYPE = "image/x-icon"

      # @since 2.1.0
      # @api private
      STYLESHEET_REL = "stylesheet"

      # @since 2.1.0
      # @api private
      FAVICON_REL = "shortcut icon"

      # @since 2.1.0
      # @api private
      DEFAULT_FAVICON = "favicon.ico"

      # @since 0.3.0
      # @api private
      CROSSORIGIN_ANONYMOUS = "anonymous"

      # @since 0.3.0
      # @api private
      # TODO: we can drop the defined?-check and fallback once Ruby 3.3 becomes our minimum required version
      ABSOLUTE_URL_MATCHER = (
        defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
      ).make_regexp

      # @since 1.1.0
      # @api private
      QUERY_STRING_MATCHER = /\?/

      include Hanami::View::Helpers::TagHelper

      # Generate `script` tag for given source(s)
      #
      # It accepts one or more strings representing the name of the asset, if it
      # comes from the application or third party gems. It also accepts strings
      # representing absolute URLs in case of public CDN (eg. jQuery CDN).
      #
      # If the "fingerprint mode" is on, `src` is the fingerprinted
      # version of the relative URL.
      #
      # If the "CDN mode" is on, the `src` is an absolute URL of the
      # application CDN.
      #
      # If the "subresource integrity mode" is on, `integrity` is the
      # name of the algorithm, then a hyphen, then the hash value of the file.
      # If more than one algorithm is used, they"ll be separated by a space.
      #
      # If the Content Security Policy uses 'nonce' and the source is not
      # absolute, the nonce value of the current request is automatically added
      # as an attribute. You can override this with the `nonce: false` option.
      # See {#content_security_policy_nonce} for more.
      #
      # @param sources [Array<String, #url>] one or more assets by name or absolute URL
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the javascript file is missing
      # from the manifest
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Single Asset
      #
      #   <%= javascript_tag "application" %>
      #
      #   #
      #
      # @example Multiple Assets
      #
      #   <%= javascript_tag "application", "dashboard" %>
      #
      #   #
      #   #
      #
      # @example Asynchronous Execution
      #
      #   <%= javascript_tag "application", async: true %>
      #
      #   #
      #
      # @example Subresource Integrity
      #
      #   <%= javascript_tag "application" %>
      #
      #   #
      #
      # @example Subresource Integrity for 3rd Party Scripts
      #
      #   <%= javascript_tag "https://example.com/assets/example.js", integrity: "sha384-oqVu...Y8wC" %>
      #
      #   #
      #
      # @example Deferred Execution
      #
      #   <%= javascript_tag "application", defer: true %>
      #
      #   #
      #
      # @example Disable nonce
      #
      #   <%= javascript_tag "application", nonce: false %>
      #
      # @example Absolute URL
      #
      #   <%= javascript_tag "https://code.jquery.com/jquery-2.1.4.min.js" %>
      #
      #   #
      #
      # @example Fingerprint Mode
      #
      #   <%= javascript_tag "application" %>
      #
      #   #
      #
      # @example CDN Mode
      #
      #   <%= javascript_tag "application" %>
      #
      #   #
      def javascript_tag(*sources, **options)
        options = options.reject { |k, _| k.to_sym == :src }
        nonce_option = options.delete(:nonce)

        _safe_tags(*sources) do |source|
          attributes = {
            src: _typed_url(source, JAVASCRIPT_EXT),
            type: JAVASCRIPT_MIME_TYPE,
            nonce: _nonce(source, nonce_option)
          }
          attributes.merge!(options)

          if _context.assets.subresource_integrity? || attributes.include?(:integrity)
            attributes[:integrity] ||= _subresource_integrity_value(source, JAVASCRIPT_EXT)
            attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
          end

          tag.script(**attributes).to_s
        end
      end

      # Generate `link` tag for given source(s)
      #
      # It accepts one or more strings representing the name of the asset, if it
      # comes from the application or third party gems. It also accepts strings
      # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
      #
      # If the "fingerprint mode" is on, `href` is the fingerprinted
      # version of the relative URL.
      #
      # If the "CDN mode" is on, the `href` is an absolute URL of the
      # application CDN.
      #
      # If the "subresource integrity mode" is on, `integrity` is the
      # name of the algorithm, then a hyphen, then the hashed value of the file.
      # If more than one algorithm is used, they"ll be separated by a space.
      #
      # If the Content Security Policy uses 'nonce' and the source is not
      # absolute, the nonce value of the current request is automatically added
      # as an attribute. You can override this with the `nonce: false` option.
      # See {#content_security_policy_nonce} for more.
      #
      # @param sources [Array<String, #url>] one or more assets by name or absolute URL
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the stylesheet file is missing
      # from the manifest
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Single Asset
      #
      #   <%= stylesheet_tag "application" %>
      #
      #   # <link href="/assets/application.css" type="text/css" rel="stylesheet">
      #
      # @example Multiple Assets
      #
      #   <%= stylesheet_tag "application", "dashboard" %>
      #
      #   # <link href="/assets/application.css" type="text/css" rel="stylesheet">
      #   # <link href="/assets/dashboard.css" type="text/css" rel="stylesheet">
      #
      # @example Disable nonce
      #
      #   <%= stylesheet_tag "application", nonce: false %>
      #
      # @example Subresource Integrity
      #
      #   <%= stylesheet_tag "application" %>
      #
      #   # <link href="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
      #   #       type="text/css" integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
      #
      # @example Subresource Integrity for 3rd Party Assets
      #
      #   <%= stylesheet_tag "https://example.com/assets/example.css", integrity: "sha384-oqVu...Y8wC" %>
      #
      #   # <link href="https://example.com/assets/example.css"
      #   #       type="text/css" rel="stylesheet" integrity="sha384-oqVu...Y8wC" crossorigin="anonymous"></script>
      #
      # @example Absolute URL
      #
      #   <%= stylesheet_tag "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" %>
      #
      #   # <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
      #   #       type="text/css" rel="stylesheet">
      #
      # @example Fingerprint Mode
      #
      #   <%= stylesheet_tag "application" %>
      #
      #   # <link href="/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css" type="text/css" rel="stylesheet">
      #
      # @example CDN Mode
      #
      #   <%= stylesheet_tag "application" %>
      #
      #   # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
      #   #       type="text/css" rel="stylesheet">
      def stylesheet_tag(*sources, **options)
        options = options.reject { |k, _| k.to_sym == :href }
        nonce_option = options.delete(:nonce)

        _safe_tags(*sources) do |source|
          attributes = {
            href: _typed_url(source, STYLESHEET_EXT),
            type: STYLESHEET_MIME_TYPE,
            rel: STYLESHEET_REL,
            nonce: _nonce(source, nonce_option)
          }
          attributes.merge!(options)

          if _context.assets.subresource_integrity? || attributes.include?(:integrity)
            attributes[:integrity] ||= _subresource_integrity_value(source, STYLESHEET_EXT)
            attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
          end

          tag.link(**attributes).to_s
        end
      end

      # Generate `img` tag for given source
      #
      # It accepts one string representing the name of the asset, if it comes
      # from the application or third party gems. It also accepts strings
      # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
      #
      # `alt` Attribute is auto generated from `src`.
      # You can specify a different value, by passing the `:src` option.
      #
      # If the "fingerprint mode" is on, `src` is the fingerprinted
      # version of the relative URL.
      #
      # If the "CDN mode" is on, the `src` is an absolute URL of the
      # application CDN.
      #
      # @param source [String, #url] asset name, absolute URL, or asset object
      # @param options [Hash] HTML 5 attributes
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the image file is missing
      # from the manifest
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Basic Usage
      #
      #   <%= image_tag "logo.png" %>
      #
      #   #
      #
      # @example Custom alt Attribute
      #
      #   <%= image_tag "logo.png", alt: "Application Logo" %>
      #
      #   #
      #
      # @example Custom HTML Attributes
      #
      #   <%= image_tag "logo.png", id: "logo", class: "image" %>
      #
      #   #
      #
      # @example Absolute URL
      #
      #   <%= image_tag "https://example-cdn.com/images/logo.png" %>
      #
      #   #
      #
      # @example Fingerprint Mode
      #
      #   <%= image_tag "logo.png" %>
      #
      #   #
      #
      # @example CDN Mode
      #
      #   <%= image_tag "logo.png" %>
      #
      #   #
      def image_tag(source, options = {})
        options = options.reject { |k, _| k.to_sym == :src }
        attributes = {
          src: asset_url(source),
          alt: _context.inflector.humanize(::File.basename(source, WILDCARD_EXT))
        }
        attributes.merge!(options)

        tag.img(**attributes)
      end

      # Generate `link` tag application favicon.
      #
      # If no argument is given, it assumes `favico.ico` from the application.
      #
      # It accepts one string representing the name of the asset.
      #
      # If the "fingerprint mode" is on, `href` is the fingerprinted version
      # of the relative URL.
      #
      # If the "CDN mode" is on, the `href` is an absolute URL of the
      # application CDN.
      #
      # @param source [String, #url] asset name or asset object
      # @param options [Hash] HTML 5 attributes
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the favicon is file missing
      # from the manifest
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Basic Usage
      #
      #   <%= favicon_tag %>
      #
      #   # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
      #
      # @example Custom Path
      #
      #   <%= favicon_tag "fav.ico" %>
      #
      #   # <link href="/assets/fav.ico" rel="shortcut icon" type="image/x-icon">
      #
      # @example Custom HTML Attributes
      #
      #   <%= favicon_tag "favicon.ico", id: "fav" %>
      #
      #   # <link id="fav" href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
      #
      # @example Fingerprint Mode
      #
      #   <%= favicon_tag %>
      #
      #   # <link href="/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico" rel="shortcut icon" type="image/x-icon">
      #
      # @example CDN Mode
      #
      #   <%= favicon_tag %>
      #
      #   # <link href="https://assets.bookshelf.org/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico"
      #           rel="shortcut icon" type="image/x-icon">
      def favicon_tag(source = DEFAULT_FAVICON, options = {})
        options = options.reject { |k, _| k.to_sym == :href }

        attributes = {
          href: asset_url(source),
          rel: FAVICON_REL,
          type: FAVICON_MIME_TYPE
        }
        attributes.merge!(options)

        tag.link(**attributes)
      end

      # Generate `video` tag for given source
      #
      # It accepts one string representing the name of the asset, if it comes
      # from the application or third party gems. It also accepts strings
      # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
      #
      # Alternatively, it accepts a block that allows to specify one or more
      # sources via the `source` tag.
      #
      # If the "fingerprint mode" is on, `src` is the fingerprinted
      # version of the relative URL.
      #
      # If the "CDN mode" is on, the `src` is an absolute URL of the
      # application CDN.
      #
      # @param source [String, #url] asset name, absolute URL or asset object
      # @param options [Hash] HTML 5 attributes
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the video file is missing
      # from the manifest
      #
      # @raise [ArgumentError] if source isn"t specified both as argument or
      #   tag inside the given block
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Basic Usage
      #
      #   <%= video_tag "movie.mp4" %>
      #
      #   # <video src="/assets/movie.mp4"></video>
      #
      # @example Absolute URL
      #
      #   <%= video_tag "https://example-cdn.com/assets/movie.mp4" %>
      #
      #   # <video src="https://example-cdn.com/assets/movie.mp4"></video>
      #
      # @example Custom HTML Attributes
      #
      #   <%= video_tag("movie.mp4", autoplay: true, controls: true) %>
      #
      #   # <video src="/assets/movie.mp4" autoplay="autoplay" controls="controls"></video>
      #
      # @example Fallback Content
      #
      #   <%=
      #     video("movie.mp4") do
      #       "Your browser does not support the video tag"
      #     end
      #   %>
      #
      #   # <video src="/assets/movie.mp4">
      #   #  Your browser does not support the video tag
      #   # </video>
      #
      # @example Tracks
      #
      #   <%=
      #     video("movie.mp4") do
      #       tag.track(kind: "captions", src: asset_url("movie.en.vtt"),
      #             srclang: "en", label: "English")
      #     end
      #   %>
      #
      #   # <video src="/assets/movie.mp4">
      #   #   <track kind="captions" src="/assets/movie.en.vtt" srclang="en" label="English">
      #   # </video>
      #
      # @example Without Any Argument
      #
      #   <%= video_tag %>
      #
      #   # ArgumentError
      #
      # @example Without src And Without Block
      #
      #   <%= video_tag(content: true) %>
      #
      #   # ArgumentError
      #
      # @example Fingerprint Mode
      #
      #   <%= video_tag "movie.mp4" %>
      #
      #   # <video src="/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
      #
      # @example CDN Mode
      #
      #   <%= video_tag "movie.mp4" %>
      #
      #   # <video src="https://assets.bookshelf.org/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
      def video_tag(source = nil, options = {}, &blk)
        options = _source_options(source, options, &blk)
        tag.video(**options, &blk)
      end

      # Generate `audio` tag for given source
      #
      # It accepts one string representing the name of the asset, if it comes
      # from the application or third party gems. It also accepts strings
      # representing absolute URLs in case of public CDN (eg. Bootstrap CDN).
      #
      # Alternatively, it accepts a block that allows to specify one or more
      # sources via the `source` tag.
      #
      # If the "fingerprint mode" is on, `src` is the fingerprinted
      # version of the relative URL.
      #
      # If the "CDN mode" is on, the `src` is an absolute URL of the
      # application CDN.
      #
      # @param source [String, #url] asset name, absolute URL or asset object
      # @param options [Hash] HTML 5 attributes
      #
      # @return [Hanami::View::HTML::SafeString] the markup
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the audio file is missing
      # from the manifest
      #
      # @raise [ArgumentError] if source isn"t specified both as argument or
      #   tag inside the given block
      #
      # @since 2.1.0
      #
      # @see Hanami::Assets::Helpers#path
      #
      # @example Basic Usage
      #
      #   <%= audio_tag "song.ogg" %>
      #
      #   # <audio src="/assets/song.ogg"></audio>
      #
      # @example Absolute URL
      #
      #   <%= audio_tag "https://example-cdn.com/assets/song.ogg" %>
      #
      #   # <audio src="https://example-cdn.com/assets/song.ogg"></audio>
      #
      # @example Custom HTML Attributes
      #
      #   <%= audio_tag("song.ogg", autoplay: true, controls: true) %>
      #
      #   # <audio src="/assets/song.ogg" autoplay="autoplay" controls="controls"></audio>
      #
      # @example Fallback Content
      #
      #   <%=
      #     audio("song.ogg") do
      #       "Your browser does not support the audio tag"
      #     end
      #   %>
      #
      #   # <audio src="/assets/song.ogg">
      #   #  Your browser does not support the audio tag
      #   # </audio>
      #
      # @example Tracks
      #
      #   <%=
      #     audio("song.ogg") do
      #       tag.track(kind: "captions", src: asset_url("song.pt-BR.vtt"),
      #             srclang: "pt-BR", label: "Portuguese")
      #     end
      #   %>
      #
      #   # <audio src="/assets/song.ogg">
      #   #   <track kind="captions" src="/assets/song.pt-BR.vtt" srclang="pt-BR" label="Portuguese">
      #   # </audio>
      #
      # @example Without Any Argument
      #
      #   <%= audio_tag %>
      #
      #   # ArgumentError
      #
      # @example Without src And Without Block
      #
      #   <%= audio_tag(controls: true) %>
      #
      #   # ArgumentError
      #
      # @example Fingerprint Mode
      #
      #   <%= audio_tag "song.ogg" %>
      #
      #   # <audio src="/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
      #
      # @example CDN Mode
      #
      #   <%= audio_tag "song.ogg" %>
      #
      #   # <audio src="https://assets.bookshelf.org/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
      def audio_tag(source = nil, options = {}, &blk)
        options = _source_options(source, options, &blk)
        tag.audio(**options, &blk)
      end

      # It generates the relative or absolute URL for the given asset.
      # It automatically decides if it has to use the relative or absolute
      # depending on the configuration and current environment.
      #
      # Absolute URLs are returned as they are.
      #
      # It can be the name of the asset, coming from the sources or third party
      # gems.
      #
      # If Fingerprint mode is on, it returns the fingerprinted path of the source
      #
      # If CDN mode is on, it returns the absolute URL of the asset.
      #
      # @param source [String, #url] the asset name or asset object
      #
      # @return [String] the asset path
      #
      # @raise [Hanami::Assets::MissingManifestAssetError] if `fingerprint` or
      # `subresource_integrity` modes are on and the asset is missing
      # from the manifest
      #
      # @since 2.1.0
      #
      # @example Basic Usage
      #
      #   <%= asset_url "application.js" %>
      #
      #   # "/assets/application.js"
      #
      # @example Alias
      #
      #   <%= asset_url "application.js" %>
      #
      #   # "/assets/application.js"
      #
      # @example Absolute URL
      #
      #   <%= asset_url "https://code.jquery.com/jquery-2.1.4.min.js" %>
      #
      #   # "https://code.jquery.com/jquery-2.1.4.min.js"
      #
      # @example Fingerprint Mode
      #
      #   <%= asset_url "application.js" %>
      #
      #   # "/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
      #
      # @example CDN Mode
      #
      #   <%= asset_url "application.js" %>
      #
      #   # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
      def asset_url(source)
        return source.url if source.respond_to?(:url)
        return source if _absolute_url?(source)

        _context.assets[source].url
      end

      # Random per request nonce value for Content Security Policy (CSP) rules.
      #
      # If the `Hanami::Middleware::ContentSecurityPolicyNonce` middleware is
      # in use, this helper returns the nonce value for the current request
      # or `nil` otherwise.
      #
      # For this policy to work in the browser, you have to add the `'nonce'`
      # placeholder to the script and/or style source policy rule. It will be
      # substituted by the current nonce value like `'nonce-A12OggyZ'.
      #
      # @return [String, nil] nonce value of the current request
      #
      # @since 2.3.0
      #
      # @example App configuration
      #
      #   config.middleware.use Hanami::Middleware::ContentSecurityPolicyNonce
      #   config.actions.content_security_policy[:script_src] = "'self' 'nonce'"
      #   config.actions.content_security_policy[:style_src] = "'self' 'nonce'"
      #
      # @example View helper
      #
      #


def stylesheet_tag(*sources, **options)
        options = options.reject { |k, _| k.to_sym == :href }
        nonce_option = options.delete(:nonce)

        _safe_tags(*sources) do |source|
          attributes = {
            href: _typed_url(source, STYLESHEET_EXT),
            type: STYLESHEET_MIME_TYPE,
            rel: STYLESHEET_REL,
            nonce: _nonce(source, nonce_option)
          }
          attributes.merge!(options)

          if _context.assets.subresource_integrity? || attributes.include?(:integrity)
            attributes[:integrity] ||= _subresource_integrity_value(source, STYLESHEET_EXT)
            attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
          end

          tag.link(**attributes).to_s
        end
      end


def favicon_tag(source = DEFAULT_FAVICON, options = {})
        options = options.reject { |k, _| k.to_sym == :href }

        attributes = {
          href: asset_url(source),
          rel: FAVICON_REL,
          type: FAVICON_MIME_TYPE
        }
        attributes.merge!(options)

        tag.link(**attributes)
      end


# frozen_string_literal: true

module Hanami
  class Config
    class Actions
      # Config for Content Security Policy in Hanami apps
      #
      # @since 2.0.0
      class ContentSecurityPolicy
        # @since 2.0.0
        # @api private
        def initialize(&blk)
          @policy = {
            base_uri: "'self'",
            child_src: "'self'",
            connect_src: "'self'",
            default_src: "'none'",
            font_src: "'self'",
            form_action: "'self'",
            frame_ancestors: "'self'",
            frame_src: "'self'",
            img_src: "'self' https: data:",
            media_src: "'self'",
            object_src: "'none'",
            script_src: "'self'",
            style_src: "'self' 'unsafe-inline' https:"
          }

          blk&.(self)
        end

        # @since 2.0.0
        # @api private
        def initialize_copy(original_object)
          @policy = original_object.instance_variable_get(:@policy).dup
          super
        end

        # Get a CSP setting
        #
        # @param key [Symbol] the underscored name of the CPS setting
        # @return [String,NilClass] the CSP setting, if any
        #
        # @since 2.0.0
        # @api public
        #
        # @example
        #   module MyApp
        #     class App < Hanami::App
        #       config.actions.content_security_policy[:base_uri] # => "'self'"
        #     end
        #   end
        def [](key)
          @policy[key]
        end

        # Set a CSP setting
        #
        # @param key [Symbol] the underscored name of the CPS setting
        # @param value [String] the CSP setting value
        #
        # @since 2.0.0
        # @api public
        #
        # @example Replace a default value
        #   module MyApp
        #     class App < Hanami::App
        #       config.actions.content_security_policy[:plugin_types] = nil
        #     end
        #   end
        #
        # @example Append to a default value
        #   module MyApp
        #     class App < Hanami::App
        #       config.actions.content_security_policy[:script_src] += " https://my.cdn.test"
        #     end
        #   end
        def []=(key, value)
          @policy[key] = value
        end

        # Deletes a CSP key
        #
        # @param key [Symbol] the underscored name of the CPS setting
        #
        # @since 2.0.0
        # @api public
        #
        # @example
        #   module MyApp
        #     class App < Hanami::App
        #       config.actions.content_security_policy.delete(:object_src)
        #     end
        #   end
        def delete(key)
          @policy.delete(key)
        end

        # Returns true if 'nonce' is used in any of the policies.
        #
        # @return [Boolean]
        #
        # @api public
        # @since 2.3.0
        def nonce?
          @policy.any? { _2.match?(/'nonce'/) }
        end

        # Returns an array of middleware name to support 'nonce' in
        # policies, or an empty array if 'nonce' is not used.
        #
        # @return [Array<(Symbol, Array)>]
        #
        # @api public
        # @since 2.3.0
        def middleware
          return [] unless nonce?

          [Hanami::Middleware::ContentSecurityPolicyNonce]
        end

        # @since 2.0.0
        # @api private
        def to_s
          @policy.map do |key, value|
            "#{dasherize(key)} #{value}"
          end.join(";")
        end

        private

        # @since 2.0.0
        # @api private
        def dasherize(key)
          key.to_s.gsub("_", "-")
        end
      end
    end
  end
end


# frozen_string_literal: true

module Hanami
  module Providers
    class I18n < Hanami::Provider::Source
      # A wrapper that provides a full `I18n`-like interface for an individual I18n backend. This
      # allows each Hanami slice to have its own isolated I18n instance.
      #
      # Unlike the global I18n module which uses class variables for configuration,
      # this wrapper maintains per-instance state for true isolation between slices.
      #
      # @api public
      # @since x.x.x
      class Backend
        attr_reader :backend

        # Returns the default locale.
        #
        # @return [Symbol] the default locale
        #
        # @api public
        # @since x.x.x
        attr_reader :default_locale

        # Creates a new Backend instance.
        #
        # @param backend [I18n::Backend::Base] the underlying I18n backend
        # @param locale [Symbol, String, nil] initial locale to set in thread-local storage
        # @param default_locale [Symbol, String] the default locale to use when no locale is set
        # @param available_locales [Array<Symbol, String>] list of available locales
        # @param fallbacks [I18n::Locale::Fallbacks, nil] fallbacks configuration for missing translations
        #
        # @api private
        # @since x.x.x
        def initialize(backend, locale: nil, default_locale: :en, available_locales: [], fallbacks: nil)
          @backend = backend
          @default_locale = default_locale.to_sym
          @available_locales = Array(available_locales).map(&:to_sym)
          @fallbacks = fallbacks

          # Set initial locale (if provided) in thread-local storage.
          @storage_key = :"hanami_i18n_#{object_id}"
          self.locale = locale if locale
        end

        # rubocop:disable Metrics/PerceivedComplexity

        # Translates the given key.
        #
        # @param key [String, Symbol] the translation key to look up
        # @param options [Hash] translation options
        # @option options [Symbol, String] :locale the locale to use (defaults to current locale)
        # @option options [String, Proc, Array] :default default value if translation is missing
        # @option options [Hash] :scope additional scope for the key
        # @option options [Integer] :count for pluralization
        # @option options [Boolean] :raise whether to raise an exception for missing translations
        #
        # @return [String, Object] the translated string or default value
        #
        # @raise [I18n::MissingTranslationData] if translation is missing and :raise option is true
        #
        # @example Basic translation
        #   translate("hello") # => "Hello"
        #
        # @example Translation with interpolation
        #   translate("greeting", name: "Alice") # => "Hello, Alice"
        #
        # @example With explicit locale
        #   translate("hello", locale: :fr) # => "Bonjour"
        #
        # @api public
        # @since x.x.x
        def translate(key, **options)
          locale = options[:locale] || self.locale

          result = catch(:exception) do
            @backend.translate(locale, key, options)
          end

          # If translation is missing and fallbacks are configured, try fallback locales
          if result.is_a?(::I18n::MissingTranslation) && @fallbacks && !options[:fallback]
            fallback_locales = @fallbacks[locale] - [locale]
            fallback_locales.each do |fallback_locale|
              fallback_result = catch(:exception) do
                @backend.translate(fallback_locale, key, options.merge(fallback: true))
              end

              unless fallback_result.is_a?(::I18n::MissingTranslation)
                return fallback_result
              end
            end
          end

          if result.is_a?(::I18n::MissingTranslation)
            if options[:raise]
              raise ::I18n::MissingTranslationData.new(locale, key, options)
            else
              handle_missing_translation(result, options)
            end
          else
            result
          end
        end

        # rubocop:enable Metrics/PerceivedComplexity

        # @api public
        # @since x.x.x
        alias_method :t, :translate

        # Translates the given key, raising an exception if translation is missing.
        #
        # @param key [String, Symbol] the translation key to look up
        # @param options [Hash] translation options (see {#translate})
        #
        # @return [String, Object] the translated string
        #
        # @raise [I18n::MissingTranslationData] if translation is missing
        #
        # @example
        #   t!("hello") # => "Hello"
        #   t!("missing.key") # raises I18n::MissingTranslationData
        #
        # @api public
        # @since x.x.x
        def t!(key, **options)
          translate(key, **options.merge(raise: true))
        end

        # Localizes the given object (e.g., date, time, number).
        #
        # @param object [Date, Time, DateTime, Numeric] the object to localize
        # @param locale [Symbol, String, nil] the locale to use (defaults to current locale)
        # @param format [Symbol, String, nil] the format to use for localization
        # @param options [Hash] additional localization options
        #
        # @return [String] the localized string representation
        #
        # @example Localize a date
        #   localize(Date.today, format: :long) # => "January 19, 2026"
        #
        # @example Localize with specific locale
        #   localize(Date.today, locale: :fr, format: :long) # => "19 janvier 2026"
        #
        # @api public
        # @since x.x.x
        def localize(object, locale: nil, format: nil, **options)
          locale ||= self.locale
          @backend.localize(locale, object, format, options)
        end

        # @api public
        # @since x.x.x
        alias_method :l, :localize

        # Returns true if a translation exists for the given key.
        #
        # @param key [String, Symbol] the translation key to check
        # @param locale [Symbol, String, nil] the locale to check (defaults to current locale)
        # @param options [Hash] additional options
        #
        # @return [Boolean] true if the translation exists, false otherwise
        #
        # @example
        #   exists?("hello") # => true
        #   exists?("missing.key") # => false
        #
        # @api public
        # @since x.x.x
        def exists?(key, locale: nil, **options)
          locale ||= self.locale
          @backend.exists?(locale, key, options)
        end

        # Transliterates the given string.
        #
        # @param key [String] the string to transliterate
        # @param locale [Symbol, String, nil] the locale to use (defaults to current locale)
        # @param replacement [String, nil] replacement string for non-transliteratable characters
        # @param options [Hash] additional transliteration options
        #
        # @return [String] the transliterated string
        #
        # @example
        #   transliterate("Ærøskøbing") # => "AEroskobing"
        #
        # @api public
        # @since x.x.x
        def transliterate(key, locale: nil, replacement: nil, **options)
          locale ||= self.locale
          @backend.transliterate(locale, key, replacement)
        end

        # Returns available locales.
        #
        # If configured via `config.i18n.available_locales`, returns the configured locales.
        # Otherwise, returns all locales detected from loaded translation files.
        #
        # @api public
        # @since x.x.x
        def available_locales
          if @available_locales.any?
            @available_locales
          else
            @backend.available_locales
          end
        end

        # Reloads translations from translation files.
        #
        # @return [void]
        #
        # @api public
        # @since x.x.x
        def reload!
          @backend.reload!
        end

        # Eager loads translations if the backend supports it.
        #
        # @return [void]
        #
        # @api public
        # @since x.x.x
        def eager_load!
          @backend.eager_load! if @backend.respond_to?(:eager_load!)
        end

        # Returns the current locale from fiber/thread-local storage, or default_locale.
        #
        # This is thread-safe and works correctly with concurrent requests.
        # Each backend instance maintains its own locale in thread storage.
        #
        # @return [Symbol] the current locale or default locale if none is set
        #
        # @example
        #   locale # => :en
        #   self.locale = :fr
        #   locale # => :fr
        #
        # @api public
        # @since x.x.x
        def locale
          Thread.current[@storage_key] || default_locale
        end

        # Sets the current locale in fiber/thread-local storage.
        #
        # This is thread-safe and works correctly with concurrent requests.
        # Each backend instance maintains its own locale in thread storage.
        #
        # @param value [Symbol, String, nil] the locale to set (converted to symbol)
        #
        # @return [Symbol, nil] the locale value that was set
        #
        # @example
        #   self.locale = :fr
        #   self.locale = "de"
        #   self.locale = nil # resets to default_locale
        #
        # @api public
        # @since x.x.x
        def locale=(value)
          Thread.current[@storage_key] = value&.to_sym
        end

        # Executes a block with a temporary locale.
        #
        # This is useful for executing code with a specific locale without affecting
        # other concurrent requests. The previous locale is restored even if the block raises.
        #
        # @param tmp_locale [Symbol, String, nil] the temporary locale to use
        #
        # @yieldreturn [Object] the return value of the block
        #
        # @return [Object] the return value of the block
        #
        # @example
        #   with_locale(:fr) do
        #     t("greeting") # Uses French locale
        #   end
        #   # locale is restored to previous value
        #
        # @example Nested usage
        #   with_locale(:fr) do
        #     with_locale(:de) do
        #       t("hello") # Uses German
        #     end
        #     t("hello") # Uses French
        #   end
        #
        # @api public
        # @since x.x.x
        def with_locale(tmp_locale)
          previous_locale = Thread.current[@storage_key]
          Thread.current[@storage_key] = tmp_locale&.to_sym
          yield
        ensure
          Thread.current[@storage_key] = previous_locale
        end

        private

        def handle_missing_translation(missing_translation, options)
          default = options[:default]
          return default if default.is_a?(String)

          key = missing_translation.key
          key.to_s
        end
      end
    end
  end
end


# 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


# 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


# 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


# 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


# 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


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


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