# frozen_string_literal: true

require 'capybara/selenium/extensions/html5_drag'
require 'capybara/selenium/extensions/file_input_click_emulation'

class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
  include Html5Drag
  include FileInputClickEmulation

  def set_text(value, clear: nil, **_unused)
    super.tap do
      # React doesn't see the chromedriver element clear
      send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
    end
  end

  def set_file(value) # rubocop:disable Naming/AccessorMethodName
    # In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
    if browser_version >= 75.0
      driver.execute_script(<<~JS, self)
        if (arguments[0].multiple && arguments[0].files.length){
          arguments[0].value = null;
        }
      JS
    end
    super
  end

  def drop(*args)
    html5_drop(*args)
  end

  def click(*, **)
    super
  rescue ::Selenium::WebDriver::Error::ElementClickInterceptedError
    raise
  rescue ::Selenium::WebDriver::Error::WebDriverError => e
    # chromedriver 74 (at least on mac) raises the wrong error for this
    if e.message.include?('element click intercepted')
      raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
    end

    raise
  end

  def disabled?
    driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
  end

  def select_option
    # To optimize to only one check and then click
    selected_or_disabled = driver.evaluate_script(<<~JS, self)
      arguments[0].matches(':disabled, select:disabled *, :checked')
    JS
    click unless selected_or_disabled
  end

  def visible?
    return super unless native_displayed?

    begin
      bridge.send(:execute, :is_element_displayed, id: native_id)
    rescue Selenium::WebDriver::Error::UnknownCommandError
      # If the is_element_displayed command is unknown, no point in trying again
      driver.options[:native_displayed] = false
      super
    end
  end

  def send_keys(*args)
    args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
        .each do |contains_emoji, inputs|
      if contains_emoji
        inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
              .each do |emoji, clusters|
          if emoji
            driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
          else
            super(clusters.join)
          end
        end
      else
        super(*inputs)
      end
    end
  end

protected

  def catch_error?(error, errors = nil)
    # Selenium::WebDriver::Error::UnknownError:
    #    unknown error: unhandled inspector error: {"code":-32000,"message":"Node with given id does not belong to the document"}
    super ||
      (error.is_a?(Selenium::WebDriver::Error::UnknownError) &&
       error.message.include?('Node with given id does not belong to the document'))
  end

private

  def perform_legacy_drag(element, drop_modifiers)
    return super if chromedriver_fixed_actions_key_state? || element.obscured?

    raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?

    # W3C Chrome/chromedriver < 77 doesn't maintain mouse button state across actions API performs
    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2981
    browser_action.release.perform
    browser_action.click_and_hold(native).move_to(element.native).release.perform
  end

  def browser_version(to_float: true)
    caps = capabilities
    ver = caps[:browser_version] || caps[:version]
    ver = ver.to_f if to_float
    ver
  end

  def chromedriver_fixed_actions_key_state?
    Gem::Requirement.new('>= 76.0.3809.68').satisfied_by?(chromedriver_version)
  end

  def chromedriver_supports_displayed_endpoint?
    Gem::Requirement.new('>= 76.0.3809.25').satisfied_by?(chromedriver_version)
  end

  def chromedriver_version
    Gem::Version.new(capabilities['chrome']['chromedriverVersion'].split(' ')[0]) # rubocop:disable Style/RedundantArgument
  end

  def native_displayed?
    (driver.options[:native_displayed] != false) &&
      chromedriver_supports_displayed_endpoint? &&
      !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
  end
end

class Capybara::Selenium::ChromeNode < < Capybara::Selenium::Node


def click(*, **)
    super
  rescue ::Selenium::WebDriver::Error::ElementClickInterceptedError
    raise
  rescue ::Selenium::WebDriver::Error::WebDriverError => e
    # chromedriver 74 (at least on mac) raises the wrong error for this
    if e.message.include?('element click intercepted')
      raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
    end

    raise
  end


def send_keys(*args)
    args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
        .each do |contains_emoji, inputs|
      if contains_emoji
        inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
              .each do |emoji, clusters|
          if emoji
            driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
          else
            super(clusters.join)
          end
        end
      else
        super(*inputs)
      end
    end
  end


# frozen_string_literal: true

require 'capybara/selenium/extensions/html5_drag'

class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
  include Html5Drag

  def set_text(value, clear: nil, **_unused)
    return super unless chrome_edge?

    super.tap do
      # React doesn't see the chromedriver element clear
      send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
    end
  end

  def set_file(value) # rubocop:disable Naming/AccessorMethodName
    # In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
    if chrome_edge?
      driver.execute_script(<<~JS, self)
        if (arguments[0].multiple && arguments[0].files.length){
          arguments[0].value = null;
        }
      JS
    end
    super
  end

  def drop(*args)
    return super unless chrome_edge?

    html5_drop(*args)
  end

  def click(*, **)
    super
  rescue Selenium::WebDriver::Error::InvalidArgumentError => e
    tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
    if tag_name == 'input' && type == 'file'
      raise Selenium::WebDriver::Error::InvalidArgumentError, "EdgeChrome can't click on file inputs.\n#{e.message}"
    end

    raise
  end

  def disabled?
    return super unless chrome_edge?

    driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
  end

  def select_option
    return super unless chrome_edge?

    # To optimize to only one check and then click
    selected_or_disabled = driver.evaluate_script(<<~JS, self)
      arguments[0].matches(':disabled, select:disabled *, :checked')
    JS
    click unless selected_or_disabled
  end

  def visible?
    return super unless chrome_edge? && native_displayed?

    begin
      bridge.send(:execute, :is_element_displayed, id: native_id)
    rescue Selenium::WebDriver::Error::UnknownCommandError
      # If the is_element_displayed command is unknown, no point in trying again
      driver.options[:native_displayed] = false
      super
    end
  end

  def send_keys(*args)
    args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
        .each do |contains_emoji, inputs|
      if contains_emoji
        inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
              .each do |emoji, clusters|
          if emoji
            driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
          else
            super(clusters.join)
          end
        end
      else
        super(*inputs)
      end
    end
  end

private

  def browser_version
    @browser_version ||= begin
      caps = driver.browser.capabilities
      (caps[:browser_version] || caps[:version]).to_f
    end
  end

  def chrome_edge?
    browser_version >= 75
  end

  def native_displayed?
    (driver.options[:native_displayed] != false) &&
      # chromedriver_supports_displayed_endpoint? &&
      !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
  end
end

class Capybara::Selenium::EdgeNode < < Capybara::Selenium::Node


# frozen_string_literal: true

require 'capybara/selenium/extensions/html5_drag'
require 'capybara/selenium/extensions/file_input_click_emulation'

class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
  include Html5Drag
  include FileInputClickEmulation

  def click(keys = [], **options)
    super
  rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
    if tag_name == 'tr'
      warn 'You are attempting to click a table row which has issues in geckodriver/marionette - ' \
           'see https://github.com/mozilla/geckodriver/issues/1228 - Your test should probably be ' \
           'clicking on a table cell like a user would. Clicking the first cell in the row instead.'
      return find_css('th:first-child,td:first-child')[0].click(keys, **options)
    end
    raise
  end

  def disabled?
    driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
  end

  def set_file(value) # rubocop:disable Naming/AccessorMethodName
    # By default files are appended so we have to clear here if its multiple and already set
    driver.execute_script(<<~JS, self)
      if (arguments[0].multiple && arguments[0].files.length){
        arguments[0].value = null;
      }
    JS
    return super if browser_version >= 62.0

    # Workaround lack of support for multiple upload by uploading one at a time
    path_names = value.to_s.empty? ? [] : Array(value)
    if (fd = bridge.file_detector) && !driver.browser.respond_to?(:upload)
      path_names.map! { |path| upload(fd.call([path])) || path }
    end
    path_names.each { |path| native.send_keys(path) }
  end

  def focused?
    driver.evaluate_script('arguments[0] == document.activeElement', self)
  end

  def send_keys(*args)
    # https://github.com/mozilla/geckodriver/issues/846
    return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)

    native.click unless focused?

    _send_keys(args).perform
  end

  def drop(*args)
    html5_drop(*args)
  end

  def hover
    return super unless browser_version >= 65.0

    # work around issue 2156 - https://github.com/teamcapybara/capybara/issues/2156
    scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
  end

  def select_option
    # To optimize to only one check and then click
    selected_or_disabled = driver.evaluate_script(<<~JS, self)
      arguments[0].matches(':disabled, select:disabled *, :checked')
    JS
    click unless selected_or_disabled
  end

  def visible?
    return super unless native_displayed?

    begin
      bridge.send(:execute, :is_element_displayed, id: native_id)
    rescue Selenium::WebDriver::Error::UnknownCommandError
      # If the is_element_displayed command is unknown, no point in trying again
      driver.options[:native_displayed] = false
      super
    end
  end

private

  def native_displayed?
    (driver.options[:native_displayed] != false) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
  end

  def perform_with_options(click_options)
    # Firefox/marionette has an issue clicking with offset near viewport edge
    # scroll element to middle just in case
    scroll_to_center if click_options.coords?
    super
  end

  def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
    case keys
    when :control, :left_control, :right_control,
         :alt, :left_alt, :right_alt,
         :shift, :left_shift, :right_shift,
         :meta, :left_meta, :right_meta,
         :command
      down_keys.press(keys)
      actions.key_down(keys)
    when String
      # https://bugzilla.mozilla.org/show_bug.cgi?id=1405370
      keys = keys.upcase if (browser_version < 64.0) && down_keys&.include?(:shift)
      actions.send_keys(keys)
    when Symbol
      actions.send_keys(keys)
    when Array
      down_keys.push
      keys.each { |sub_keys| _send_keys(sub_keys, actions, down_keys) }
      down_keys.pop.reverse_each { |key| actions.key_up(key) }
    else
      raise ArgumentError, 'Unknown keys type'
    end
    actions
  end

  def upload(local_file)
    return nil unless local_file
    raise ArgumentError, "You may only upload files: #{local_file.inspect}" unless File.file?(local_file)

    file = ::Selenium::WebDriver::Zipper.zip_file(local_file)
    bridge.http.call(:post, "session/#{bridge.session_id}/file", file: file)['value']
  end

  def browser_version
    driver.browser.capabilities[:browser_version].to_f
  end
end

class Capybara::Selenium::FirefoxNode < < Capybara::Selenium::Node


def set_file(value) # rubocop:disable Naming/AccessorMethodName
    # By default files are appended so we have to clear here if its multiple and already set
    driver.execute_script(<<~JS, self)
      if (arguments[0].multiple && arguments[0].files.length){
        arguments[0].value = null;
      }
    JS
    return super if browser_version >= 62.0

    # Workaround lack of support for multiple upload by uploading one at a time
    path_names = value.to_s.empty? ? [] : Array(value)
    if (fd = bridge.file_detector) && !driver.browser.respond_to?(:upload)
      path_names.map! { |path| upload(fd.call([path])) || path }
    end
    path_names.each { |path| native.send_keys(path) }
  end


# frozen_string_literal: true

require 'capybara/selenium/extensions/html5_drag'

class Capybara::Selenium::IENode < Capybara::Selenium::Node
  def disabled?
    # super
    # optimize to one script call
    driver.evaluate_script <<~JS.delete("\n"), self
      arguments[0].msMatchesSelector('
        :disabled,
        select:disabled *,
        optgroup:disabled *,
        fieldset[disabled],
        fieldset[disabled] > *:not(legend),
        fieldset[disabled] > *:not(legend) *,
        fieldset[disabled] > legend:nth-of-type(n+2),
        fieldset[disabled] > legend:nth-of-type(n+2) *
      ')
    JS
  end
end

class Capybara::Selenium::IENode < < Capybara::Selenium::Node


# frozen_string_literal: true

# require 'capybara/selenium/extensions/html5_drag'
require 'capybara/selenium/extensions/modifier_keys_stack'

class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
  # include Html5Drag

  def click(keys = [], **options)
    # driver.execute_script('arguments[0].scrollIntoViewIfNeeded({block: "center"})', self)
    super
  rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
    if tag_name == 'tr'
      warn 'You are attempting to click a table row which has issues in safaridriver - ' \
           'Your test should probably be clicking on a table cell like a user would. ' \
           'Clicking the first cell in the row instead.'
      return find_css('th:first-child,td:first-child')[0].click(keys, **options)
    end
    raise
  rescue ::Selenium::WebDriver::Error::WebDriverError => e
    raise unless e.instance_of? ::Selenium::WebDriver::Error::WebDriverError

    # Safari doesn't return a specific error here - assume it's an ElementNotInteractableError
    raise ::Selenium::WebDriver::Error::ElementNotInteractableError,
          'Non distinct error raised in #click, translated to ElementNotInteractableError for retry'
  end

  def select_option
    # To optimize to only one check and then click
    selected_or_disabled = driver.execute_script(<<~JS, self)
      arguments[0].closest('select').scrollIntoView();
      return arguments[0].matches(':disabled, select:disabled *, :checked');
    JS
    click unless selected_or_disabled
  end

  def unselect_option
    driver.execute_script("arguments[0].closest('select').scrollIntoView()", self)
    super
  end

  def visible_text
    return '' unless visible?

    vis_text = driver.execute_script('return arguments[0].innerText', self)
    vis_text.squeeze(' ')
            .gsub(/[\ \n]*\n[\ \n]*/, "\n")
            .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
            .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
            .tr("\u00a0", ' ')
  end

  def disabled?
    driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
  end

  def set_file(value) # rubocop:disable Naming/AccessorMethodName
    # By default files are appended so we have to clear here if its multiple and already set
    native.clear if multiple? && driver.evaluate_script('arguments[0].files', self).any?
    super
  end

  def send_keys(*args)
    if args.none? { |arg| arg.is_a?(Array) || (arg.is_a?(Symbol) && MODIFIER_KEYS.include?(arg)) }
      return super(*args.map { |arg| arg == :space ? ' ' : arg })
    end

    native.click
    _send_keys(args).perform
  end

  def set_text(value, clear: nil, **_unused)
    value = value.to_s
    if clear == :backspace
      # Clear field by sending the correct number of backspace keys.
      backspaces = [:backspace] * self.value.to_s.length
      send_keys([:control, 'e'], *backspaces, value)
    else
      super.tap do
        # React doesn't see the safaridriver element clear
        send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
      end
    end
  end

  def hover
    # Workaround issue where hover would sometimes fail - possibly due to mouse not having moved
    scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
  end

private

  def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
    case keys
    when *MODIFIER_KEYS
      down_keys.press(keys)
      actions.key_down(keys)
    when String
      keys = keys.upcase if down_keys&.include?(:shift)
      actions.send_keys(keys)
    when Symbol
      actions.send_keys(keys)
    when Array
      down_keys.push
      keys.each { |sub_keys| _send_keys(sub_keys, actions, down_keys) }
      down_keys.pop.reverse_each { |key| actions.key_up(key) }
    else
      raise ArgumentError, 'Unknown keys type'
    end
    actions
  end

  MODIFIER_KEYS = %i[control left_control right_control
                     alt left_alt right_alt
                     shift left_shift right_shift
                     meta left_meta right_meta
                     command].freeze
end

class Capybara::Selenium::SafariNode < < Capybara::Selenium::Node


def set_text(value, clear: nil, **_unused)
    value = value.to_s
    if clear == :backspace
      # Clear field by sending the correct number of backspace keys.
      backspaces = [:backspace] * self.value.to_s.length
      send_keys([:control, 'e'], *backspaces, value)
    else
      super.tap do
        # React doesn't see the safaridriver element clear
        send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
      end
    end
  end


# frozen_string_literal: true

module CapybaraAtoms
private

  def read_atom(function)
    @atoms ||= Hash.new do |hash, key|
      hash[key] = begin
        File.read(File.expand_path("../../atoms/#{key}.min.js", __FILE__))
      rescue Errno::ENOENT
        super
      end
    end
    @atoms[function]
  end
end

Selenium::WebDriver::Remote::Bridge.prepend CapybaraAtoms unless ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']


# frozen_string_literal: true

module Capybara
  module Selenium
    module ChromeLogs
      LOG_MSG = <<~MSG
        Chromedriver 75+ defaults to W3C mode. Please upgrade to chromedriver >= \
        75.0.3770.90 if you need to access logs while in W3C compliant mode.
      MSG

      COMMANDS = {
        get_available_log_types: [:get, 'session/:session_id/se/log/types'],
        get_log: [:post, 'session/:session_id/se/log'],
        get_log_legacy: [:post, 'session/:session_id/log']
      }.freeze

      def commands(command)
        COMMANDS[command] || super
      end

      def available_log_types
        types = execute :get_available_log_types
        Array(types).map(&:to_sym)
      rescue ::Selenium::WebDriver::Error::UnknownCommandError
        raise NotImplementedError, LOG_MSG
      end

      def log(type)
        data = begin
          execute :get_log, {}, type: type.to_s
        rescue ::Selenium::WebDriver::Error::UnknownCommandError
          execute :get_log_legacy, {}, type: type.to_s
        end

        Array(data).map do |l|
          ::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
        rescue KeyError
          next
        end
      rescue ::Selenium::WebDriver::Error::UnknownCommandError
        raise NotImplementedError, LOG_MSG
      end
    end
  end
end


require "dotenv/version"
require "dotenv/parser"
require "dotenv/environment"
require "dotenv/missing_keys"
require "dotenv/diff"

# Shim to load environment variables from `.env files into `ENV`.
module Dotenv
  extend self

  # An internal monitor to synchronize access to ENV in multi-threaded environments.
  SEMAPHORE = Monitor.new
  private_constant :SEMAPHORE

  attr_accessor :instrumenter

  # Loads environment variables from one or more `.env` files. See `#parse` for more details.
  def load(*filenames, overwrite: false, ignore: true)
    parse(*filenames, overwrite: overwrite, ignore: ignore) do |env|
      instrument(:load, env: env) do |payload|
        update(env, overwrite: overwrite)
      end
    end
  end

  # Same as `#load`, but raises Errno::ENOENT if any files don't exist
  def load!(*filenames)
    load(*filenames, ignore: false)
  end

  # same as `#load`, but will overwrite existing values in `ENV`
  def overwrite(*filenames)
    load(*filenames, overwrite: true)
  end
  alias_method :overload, :overwrite

  # same as `#overwrite`, but raises Errno::ENOENT if any files don't exist
  def overwrite!(*filenames)
    load(*filenames, overwrite: true, ignore: false)
  end
  alias_method :overload!, :overwrite!

  # Parses the given files, yielding for each file if a block is given.
  #
  # @param filenames [String, Array<String>] Files to parse
  # @param overwrite [Boolean] Overwrite existing `ENV` values
  # @param ignore [Boolean] Ignore non-existent files
  # @param block [Proc] Block to yield for each parsed `Dotenv::Environment`
  # @return [Hash] parsed key/value pairs
  def parse(*filenames, overwrite: false, ignore: true, &block)
    filenames << ".env" if filenames.empty?
    filenames = filenames.reverse if overwrite

    filenames.reduce({}) do |hash, filename|
      begin
        env = Environment.new(File.expand_path(filename), overwrite: overwrite)
        env = block.call(env) if block
      rescue Errno::ENOENT, Errno::EISDIR
        raise unless ignore
      end

      hash.merge! env || {}
    end
  end

  # Save the current `ENV` to be restored later
  def save
    instrument(:save) do |payload|
      @diff = payload[:diff] = Dotenv::Diff.new
    end
  end

  # Restore `ENV` to a given state
  #
  # @param env [Hash] Hash of keys and values to restore, defaults to the last saved state
  # @param safe [Boolean] Is it safe to modify `ENV`? Defaults to `true` in the main thread, otherwise raises an error.
  def restore(env = @diff&.a, safe: Thread.current == Thread.main)
    # No previously saved or provided state to restore
    return unless env

    diff = Dotenv::Diff.new(b: env)
    return unless diff.any?

    unless safe
      raise ThreadError, <<~EOE.tr("\n", " ")
        Dotenv.restore is not thread safe. Use `Dotenv.modify { }` to update ENV for the duration
        of the block in a thread safe manner, or call `Dotenv.restore(safe: true)` to ignore
        this error.
      EOE
    end
    instrument(:restore, diff: diff) { ENV.replace(env) }
  end

  # Update `ENV` with the given hash of keys and values
  #
  # @param env [Hash] Hash of keys and values to set in `ENV`
  # @param overwrite [Boolean|:warn] Overwrite existing `ENV` values
  def update(env = {}, overwrite: false)
    instrument(:update) do |payload|
      diff = payload[:diff] = Dotenv::Diff.new do
        ENV.update(env.transform_keys(&:to_s)) do |key, old_value, new_value|
          # This block is called when a key exists. Return the new value if overwrite is true.
          case overwrite
          when :warn
            # not printing the value since that could be a secret
            warn "Warning: dotenv not overwriting ENV[#{key.inspect}]"
            old_value
          when true then new_value
          when false then old_value
          else raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
          end
        end
      end
      diff.env
    end
  end

  # Modify `ENV` for the block and restore it to its previous state afterwards.
  #
  # Note that the block is synchronized to prevent concurrent modifications to `ENV`,
  # so multiple threads will be executed serially.
  #
  # @param env [Hash] Hash of keys and values to set in `ENV`
  def modify(env = {}, &block)
    SEMAPHORE.synchronize do
      diff = Dotenv::Diff.new
      update(env, overwrite: true)
      block.call
    ensure
      restore(diff.a, safe: true)
    end
  end

  def require_keys(*keys)
    missing_keys = keys.flatten - ::ENV.keys
    return if missing_keys.empty?
    raise MissingKeys, missing_keys
  end

  private

  def instrument(name, payload = {}, &block)
    if instrumenter
      instrumenter.instrument("#{name}.dotenv", payload, &block)
    else
      block&.call payload
    end
  end
end

require "dotenv/rails" if defined?(Rails::Railtie)


def restore(env = @diff&.a, safe: Thread.current == Thread.main)
    # No previously saved or provided state to restore
    return unless env

    diff = Dotenv::Diff.new(b: env)
    return unless diff.any?

    unless safe
      raise ThreadError, <<~EOE.tr("\n", " ")
        Dotenv.restore is not thread safe. Use `Dotenv.modify { }` to update ENV for the duration
        of the block in a thread safe manner, or call `Dotenv.restore(safe: true)` to ignore
        this error.
      EOE
    end
    instrument(:restore, diff: diff) { ENV.replace(env) }
  end


def update(env = {}, overwrite: false)
    instrument(:update) do |payload|
      diff = payload[:diff] = Dotenv::Diff.new do
        ENV.update(env.transform_keys(&:to_s)) do |key, old_value, new_value|
          # This block is called when a key exists. Return the new value if overwrite is true.
          case overwrite
          when :warn
            # not printing the value since that could be a secret
            warn "Warning: dotenv not overwriting ENV[#{key.inspect}]"
            old_value
          when true then new_value
          when false then old_value
          else raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
          end
        end
      end
      diff.env
    end
  end


require "dotenv"
require "dotenv/version"
require "dotenv/template"
require "optparse"

module Dotenv
  # The `dotenv` command line interface. Run `$ dotenv --help` to see usage.
  class CLI < OptionParser
    attr_reader :argv, :filenames, :overwrite

    def initialize(argv = [])
      @argv = argv.dup
      @filenames = []
      @ignore = false
      @overwrite = false

      super("Usage: dotenv [options]")
      separator ""

      on("-f FILES", Array, "List of env files to parse") do |list|
        @filenames = list
      end

      on("-i", "--ignore", "ignore missing env files") do
        @ignore = true
      end

      on("-o", "--overwrite", "overwrite existing ENV variables") do
        @overwrite = true
      end
      on("--overload") { @overwrite = true }

      on("-h", "--help", "Display help") do
        puts self
        exit
      end

      on("-v", "--version", "Show version") do
        puts "dotenv #{Dotenv::VERSION}"
        exit
      end

      on("-t", "--template=FILE", "Create a template env file") do |file|
        template = Dotenv::EnvTemplate.new(file)
        template.create_template
      end

      order!(@argv)
    end

    def run
      Dotenv.load(*@filenames, overwrite: @overwrite, ignore: @ignore)
    rescue Errno::ENOENT => e
      abort e.message
    else
      exec(*@argv) unless @argv.empty?
    end
  end
end


require "active_support/log_subscriber"

module Dotenv
  # Logs instrumented events
  #
  # Usage:
  #   require "active_support/notifications"
  #   require "dotenv/log_subscriber"
  #   Dotenv.instrumenter = ActiveSupport::Notifications
  #
  class LogSubscriber < ActiveSupport::LogSubscriber
    attach_to :dotenv

    def logger
      Dotenv::Rails.logger
    end

    def load(event)
      env = event.payload[:env]

      info "Loaded #{color_filename(env.filename)}"
    end

    def update(event)
      diff = event.payload[:diff]
      changed = diff.env.keys.map { |key| color_var(key) }
      debug "Set #{changed.join(", ")}" if diff.any?
    end

    def save(event)
      info "Saved a snapshot of #{color_env_constant}"
    end

    def restore(event)
      diff = event.payload[:diff]

      removed = diff.removed.keys.map { |key| color(key, :RED) }
      restored = (diff.changed.keys + diff.added.keys).map { |key| color_var(key) }

      if removed.any? || restored.any?
        info "Restored snapshot of #{color_env_constant}"
        debug "Unset #{removed.join(", ")}" if removed.any?
        debug "Restored #{restored.join(", ")}" if restored.any?
      end
    end

    private

    def color_filename(filename)
      color(Pathname.new(filename).relative_path_from(Dotenv::Rails.root.to_s).to_s, :YELLOW)
    end

    def color_var(name)
      color(name, :CYAN)
    end

    def color_env_constant
      color("ENV", :GREEN)
    end
  end
end

class LogSubscriber < < ActiveSupport::LogSubscriber


require "dotenv/substitutions/variable"
require "dotenv/substitutions/command" if RUBY_VERSION > "1.8.7"

module Dotenv
  # Error raised when encountering a syntax error while parsing a .env file.
  class FormatError < SyntaxError; end

  # Parses the `.env` file format into key/value pairs.
  # It allows for variable substitutions, command substitutions, and exporting of variables.
  class Parser
    @substitutions = [
      Dotenv::Substitutions::Command,
      Dotenv::Substitutions::Variable
    ]

    LINE = /
      (?:^|\A)                # beginning of line
      \s*                     # leading whitespace
      (?<export>export\s+)?   # optional export
      (?<key>[\w.]+)          # key
      (?:                     # optional separator and value
        (?:\s*=\s*?|:\s+?)    #   separator
        (?<value>             #   optional value begin
          \s*'(?:\\'|[^'])*'  #     single quoted value
          |                   #     or
          \s*"(?:\\"|[^"])*"  #     double quoted value
          |                   #     or
          [^\#\n]+            #     unquoted value
        )?                    #   value end
      )?                      # separator and value end
      \s*                     # trailing whitespace
      (?:\#.*)?               # optional comment
      (?:$|\z)                # end of line
    /x

    QUOTED_STRING = /\A(['"])(.*)\1\z/m

    class << self
      attr_reader :substitutions

      def call(...)
        new(...).call
      end
    end

    def initialize(string, overwrite: false)
      # Convert line breaks to same format
      @string = string.gsub(/\r\n?/, "\n")
      @hash = {}
      @overwrite = overwrite
    end

    def call
      @string.scan(LINE) do
        match = $LAST_MATCH_INFO

        if existing?(match[:key])
          # Use value from already defined variable
          @hash[match[:key]] = ENV[match[:key]]
        elsif match[:export] && !match[:value]
          # Check for exported variable with no value
          if !@hash.member?(match[:key])
            raise FormatError, "Line #{match.to_s.inspect} has an unset variable"
          end
        else
          @hash[match[:key]] = parse_value(match[:value] || "")
        end
      end

      @hash
    end

    private

    # Determine if a variable is already defined and should not be overwritten.
    def existing?(key)
      !@overwrite && key != "DOTENV_LINEBREAK_MODE" && ENV.key?(key)
    end

    def parse_value(value)
      # Remove surrounding quotes
      value = value.strip.sub(QUOTED_STRING, '\2')
      maybe_quote = Regexp.last_match(1)

      # Expand new lines in double quoted values
      value = expand_newlines(value) if maybe_quote == '"'

      # Unescape characters and performs substitutions unless value is single quoted
      if maybe_quote != "'"
        value = unescape_characters(value)
        self.class.substitutions.each { |proc| value = proc.call(value, @hash) }
      end

      value
    end

    def unescape_characters(value)
      value.gsub(/\\([^$])/, '\1')
    end

    def expand_newlines(value)
      if (@hash["DOTENV_LINEBREAK_MODE"] || ENV["DOTENV_LINEBREAK_MODE"]) == "legacy"
        value.gsub('\n', "\n").gsub('\r', "\r")
      else
        value.gsub('\n', "\\\\\\n").gsub('\r', "\\\\\\r")
      end
    end
  end
end


def parse_value(value)
      # Remove surrounding quotes
      value = value.strip.sub(QUOTED_STRING, '\2')
      maybe_quote = Regexp.last_match(1)

      # Expand new lines in double quoted values
      value = expand_newlines(value) if maybe_quote == '"'

      # Unescape characters and performs substitutions unless value is single quoted
      if maybe_quote != "'"
        value = unescape_characters(value)
        self.class.substitutions.each { |proc| value = proc.call(value, @hash) }
      end

      value
    end


# Since rubygems doesn't support optional dependencies, we have to manually check
unless Gem::Requirement.new(">= 6.1").satisfied_by?(Gem::Version.new(Rails.version))
  warn "dotenv 3.0 only supports Rails 6.1 or later. Use dotenv ~> 2.0."
  return
end

require "dotenv/replay_logger"
require "dotenv/log_subscriber"

Dotenv.instrumenter = ActiveSupport::Notifications

# Watch all loaded env files with Spring
ActiveSupport::Notifications.subscribe("load.dotenv") do |*args|
  if defined?(Spring) && Spring.respond_to?(:watch)
    event = ActiveSupport::Notifications::Event.new(*args)
    Spring.watch event.payload[:env].filename if Rails.application
  end
end

module Dotenv
  # Rails integration for using Dotenv to load ENV variables from a file
  class Rails < ::Rails::Railtie
    delegate :files, :files=, :overwrite, :overwrite=, :autorestore, :autorestore=, :logger, to: "config.dotenv"

    def initialize
      super
      config.dotenv = ActiveSupport::OrderedOptions.new.update(
        # Rails.logger is not available yet, so we'll save log messages and replay them when it is
        logger: Dotenv::ReplayLogger.new,
        overwrite: false,
        files: [
          ".env.#{env}.local",
          (".env.local" unless env.test?),
          ".env.#{env}",
          ".env"
        ].compact,
        autorestore: env.test? && !defined?(ClimateControl) && !defined?(IceAge)
      )
    end

    # Public: Load dotenv
    #
    # This will get called during the `before_configuration` callback, but you
    # can manually call `Dotenv::Rails.load` if you needed it sooner.
    def load
      Dotenv.load(*files.map { |file| root.join(file).to_s }, overwrite: overwrite)
    end

    def overload
      deprecator.warn("Dotenv::Rails.overload is deprecated. Set `Dotenv::Rails.overwrite = true` and call Dotenv::Rails.load instead.")
      Dotenv.load(*files.map { |file| root.join(file).to_s }, overwrite: true)
    end

    # Internal: `Rails.root` is nil in Rails 4.1 before the application is
    # initialized, so this falls back to the `RAILS_ROOT` environment variable,
    # or the current working directory.
    def root
      ::Rails.root || Pathname.new(ENV["RAILS_ROOT"] || Dir.pwd)
    end

    # Set a new logger and replay logs
    def logger=(new_logger)
      logger.replay new_logger if logger.is_a?(ReplayLogger)
      config.dotenv.logger = new_logger
    end

    # The current environment that the app is running in.
    #
    # When running `rake`, the Rails application is initialized in development, so we have to
    # check which rake tasks are being run to determine the environment.
    #
    # See https://github.com/bkeepers/dotenv/issues/219
    def env
      @env ||= if defined?(Rake.application) && Rake.application.top_level_tasks.grep(TEST_RAKE_TASKS).any?
        env = Rake.application.options.show_tasks ? "development" : "test"
        ActiveSupport::EnvironmentInquirer.new(env)
      else
        ::Rails.env
      end
    end
    TEST_RAKE_TASKS = /^(default$|test(:|$)|parallel:spec|spec(:|$))/

    def deprecator # :nodoc:
      @deprecator ||= ActiveSupport::Deprecation.new
    end

    # Rails uses `#method_missing` to delegate all class methods to the
    # instance, which means `Kernel#load` gets called here. We don't want that.
    def self.load
      instance.load
    end

    initializer "dotenv", after: :initialize_logger do |app|
      if logger.is_a?(ReplayLogger)
        self.logger = ActiveSupport::TaggedLogging.new(::Rails.logger).tagged("dotenv")
      end
    end

    initializer "dotenv.deprecator" do |app|
      app.deprecators[:dotenv] = deprecator if app.respond_to?(:deprecators)
    end

    initializer "dotenv.autorestore" do |app|
      require "dotenv/autorestore" if autorestore
    end

    config.before_configuration { load }
  end

  Railtie = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Dotenv::Railtie", "Dotenv::Rails", Dotenv::Rails.deprecator)
end


module Dotenv
  EXPORT_COMMAND = "export ".freeze
  # Class for creating a template from a env file
  class EnvTemplate
    def initialize(env_file)
      @env_file = env_file
    end

    def create_template
      File.open(@env_file, "r") do |env_file|
        File.open("#{@env_file}.template", "w") do |env_template|
          env_file.each do |line|
            if is_comment?(line)
              env_template.puts line
            elsif (var = var_defined?(line))
              if line.match(EXPORT_COMMAND)
                env_template.puts "export #{var}=#{var}"
              else
                env_template.puts "#{var}=#{var}"
              end
            elsif line_blank?(line)
              env_template.puts
            end
          end
        end
      end
    end

    private

    def is_comment?(line)
      line.strip.start_with?("#")
    end

    def var_defined?(line)
      match = Dotenv::Parser::LINE.match(line)
      match && match[:key]
    end

    def line_blank?(line)
      line.strip.length.zero?
    end
  end
end


require "English"

module Dotenv
  module Substitutions
    # Substitute shell commands in a value.
    #
    #   SHA=$(git rev-parse HEAD)
    #
    module Command
      class << self
        INTERPOLATED_SHELL_COMMAND = /
          (?<backslash>\\)?   # is it escaped with a backslash?
          \$                  # literal $
          (?<cmd>             # collect command content for eval
            \(                # require opening paren
            (?:[^()]|\g<cmd>)+  # allow any number of non-parens, or balanced
                              # parens (by nesting the <cmd> expression
                              # recursively)
            \)                # require closing paren
          )
        /x

        def call(value, env)
          # Process interpolated shell commands
          value.gsub(INTERPOLATED_SHELL_COMMAND) do |*|
            # Eliminate opening and closing parentheses
            command = $LAST_MATCH_INFO[:cmd][1..-2]

            if $LAST_MATCH_INFO[:backslash]
              # Command is escaped, don't replace it.
              $LAST_MATCH_INFO[0][1..]
            else
              # Execute the command and return the value
              `#{Variable.call(command, env)}`.chomp
            end
          end
        end
      end
    end
  end
end


require "English"

module Dotenv
  module Substitutions
    # Substitute variables in a value.
    #
    #   HOST=example.com
    #   URL="https://$HOST"
    #
    module Variable
      class << self
        VARIABLE = /
          (\\)?         # is it escaped with a backslash?
          (\$)          # literal $
          (?!\()        # shouldn't be followed by parenthesis
          \{?           # allow brace wrapping
          ([A-Z0-9_]+)? # optional alpha nums
          \}?           # closing brace
        /xi

        def call(value, env)
          value.gsub(VARIABLE) do |variable|
            match = $LAST_MATCH_INFO

            if match[1] == "\\"
              variable[1..]
            elsif match[3]
              env[match[3]] || ENV[match[3]] || ""
            else
              variable
            end
          end
        end
      end
    end
  end
end


require 'hashie/extensions/array/pretty_inspect'
require 'hashie/extensions/ruby_version_check'

module Hashie
  class Array < ::Array
    include Hashie::Extensions::Array::PrettyInspect
    include Hashie::Extensions::RubyVersionCheck
    with_minimum_ruby('2.3.0') do
      def dig(*indexes)
        converted_indexes = indexes.map do |idx|
          begin
            Integer(idx)
          rescue ArgumentError
            idx
          end
        end
        super(*converted_indexes)
      end
    end
  end
end


require 'hashie/hash'

module Hashie
  #
  # A Clash is a "Chainable Lazy Hash". Inspired by libraries such as Arel,
  # a Clash allows you to chain together method arguments to build a
  # hash, something that's especially useful if you're doing something
  # like constructing a complex options hash. Here's a basic example:
  #
  #     c = Hashie::Clash.new.conditions(:foo => 'bar').order(:created_at)
  #     c # => {:conditions => {:foo => 'bar'}, :order => :created_at}
  #
  # Clash provides another way to create sub-hashes by using bang notation.
  # You can dive into a sub-hash by providing a key with a bang and dive
  # back out again with the _end! method. Example:
  #
  #     c = Hashie::Clash.new.conditions!.foo('bar').baz(123)._end!.order(:created_at)
  #     c # => { conditions: { foo: 'bar', baz: 123 }, order: :created_at}
  #
  # Because the primary functionality of Clash is to build options objects,
  # all keys are converted to symbols since many libraries expect symbols explicitly
  # for keys.
  #
  class Clash < ::Hash
    class ChainError < ::StandardError; end
    # The parent Clash if this Clash was created via chaining.
    attr_reader :_parent

    # Initialize a new clash by passing in a Hash to
    # convert and, optionally, the parent to which this
    # Clash is chained.
    def initialize(other_hash = {}, parent = nil)
      @_parent = parent
      other_hash.each_pair do |k, v|
        self[k.to_sym] = v
      end
    end

    # Jump back up a level if you are using bang method
    # chaining. For example:
    #
    # c = Hashie::Clash.new.foo('bar')
    # c.baz!.foo(123) # => c[:baz]
    # c.baz!._end! # => c
    def _end!
      _parent
    end

    def id(*args) #:nodoc:
      method_missing(:id, *args)
    end

    def merge_store(key, *args) #:nodoc:
      case args.length
      when 1
        val = args.first
        val = self.class.new(self[key]).merge(val) if self[key].is_a?(::Hash) && val.is_a?(::Hash)
      else
        val = args
      end

      self[key.to_sym] = val
      self
    end

    def method_missing(name, *args) #:nodoc:
      if args.empty? && name.to_s.end_with?('!')
        key = name[0...-1].to_sym

        case self[key]
        when NilClass
          self[key] = self.class.new({}, self)
        when Clash
          self[key]
        when Hash
          self[key] = self.class.new(self[key], self)
        else
          raise ChainError, 'Tried to chain into a non-hash key.'
        end
      elsif args.any?
        merge_store(name, *args)
      else
        super
      end
    end

    def respond_to_missing?(method_name, _include_private = false)
      method_name = method_name.to_s

      if method_name.end_with?('!')
        key = method_name[0...-1].to_sym
        [NilClass, Clash, Hash].include?(self[key].class)
      else
        true
      end
    end
  end
end


def method_missing(name, *args) #:nodoc:
      if args.empty? && name.to_s.end_with?('!')
        key = name[0...-1].to_sym

        case self[key]
        when NilClass
          self[key] = self.class.new({}, self)
        when Clash
          self[key]
        when Hash
          self[key] = self.class.new(self[key], self)
        else
          raise ChainError, 'Tried to chain into a non-hash key.'
        end
      elsif args.any?
        merge_store(name, *args)
      else
        super
      end
    end


require 'hashie/hash'
require 'set'

module Hashie
  # A Dash is a 'defined' or 'discrete' Hash, that is, a Hash
  # that has a set of defined keys that are accessible (with
  # optional defaults) and only those keys may be set or read.
  #
  # Dashes are useful when you need to create a very simple
  # lightweight data object that needs even fewer options and
  # resources than something like a DataMapper resource.
  #
  # It is preferable to a Struct because of the in-class
  # API for defining properties as well as per-property defaults.
  class Dash < Hash
    include Hashie::Extensions::PrettyInspect

    alias to_s inspect

    # Defines a property on the Dash. Options are
    # as follows:
    #
    # * <tt>:default</tt> - Specify a default value for this property,
    #   to be returned before a value is set on the property in a new
    #   Dash.
    #
    # * <tt>:required</tt> - Specify the value as required for this
    #   property, to raise an error if a value is unset in a new or
    #   existing Dash. If a Proc is provided, it will be run in the
    #   context of the Dash instance. If a Symbol is provided, the
    #   property it represents must not be nil. The property is only
    #   required if the value is truthy.
    #
    # * <tt>:message</tt> - Specify custom error message for required property
    #
    def self.property(property_name, options = {})
      properties << property_name

      if options.key?(:default)
        defaults[property_name] = options[:default]
      elsif defaults.key?(property_name)
        defaults.delete property_name
      end

      define_getter_for(property_name)
      define_setter_for(property_name)

      @subclasses.each { |klass| klass.property(property_name, options) } if defined? @subclasses

      condition = options.delete(:required)
      if condition
        message = options.delete(:message) || "is required for #{name}."
        required_properties[property_name] = { condition: condition, message: message }
      elsif options.key?(:message)
        raise ArgumentError, 'The :message option should be used with :required option.'
      end
    end

    class << self
      attr_reader :properties, :defaults
      attr_reader :getters
      attr_reader :required_properties
    end
    instance_variable_set('@properties', Set.new)
    instance_variable_set('@getters', Set.new)
    instance_variable_set('@defaults', {})
    instance_variable_set('@required_properties', {})

    def self.inherited(klass)
      super
      (@subclasses ||= Set.new) << klass
      klass.instance_variable_set('@properties', properties.dup)
      klass.instance_variable_set('@getters', getters.dup)
      klass.instance_variable_set('@defaults', defaults.dup)
      klass.instance_variable_set('@required_properties', required_properties.dup)
    end

    # Check to see if the specified property has already been
    # defined.
    def self.property?(name)
      properties.include? name
    end

    # Check to see if the specified property is
    # required.
    def self.required?(name)
      required_properties.key? name
    end

    private_class_method def self.define_getter_for(property_name)
      return if getters.include?(property_name)
      define_method(property_name) { |&block| self.[](property_name, &block) }
      getters << property_name
    end

    private_class_method def self.define_setter_for(property_name)
      setter = :"#{property_name}="
      return if instance_methods.include?(setter)
      define_method(setter) { |value| self.[]=(property_name, value) }
    end

    # You may initialize a Dash with an attributes hash
    # just like you would many other kinds of data objects.
    def initialize(attributes = {}, &block)
      super(&block)

      initialize_attributes(attributes)
      assert_required_attributes_set!
    end

    alias _regular_reader []
    alias _regular_writer []=
    private :_regular_reader, :_regular_writer

    # Retrieve a value from the Dash (will return the
    # property's default value if it hasn't been set).
    def [](property)
      assert_property_exists! property
      value = super(property)
      # If the value is a lambda, proc, or whatever answers to call, eval the thing!
      if value.is_a? Proc
        self[property] = value.call # Set the result of the call as a value
      else
        yield value if block_given?
        value
      end
    end

    # Set a value on the Dash in a Hash-like way. Only works
    # on pre-existing properties.
    def []=(property, value)
      assert_property_required! property, value
      assert_property_exists! property
      super(property, value)
    end

    def merge(other_hash)
      new_dash = dup
      other_hash.each do |k, v|
        new_dash[k] = block_given? ? yield(k, self[k], v) : v
      end
      new_dash
    end

    def merge!(other_hash)
      other_hash.each do |k, v|
        self[k] = block_given? ? yield(k, self[k], v) : v
      end
      self
    end

    def replace(other_hash)
      other_hash = self.class.defaults.merge(other_hash)
      (keys - other_hash.keys).each { |key| delete(key) }
      other_hash.each { |key, value| self[key] = value }
      self
    end

    def to_h
      defaults = ::Hash[self.class.properties.map { |prop| [prop, self.class.defaults[prop]] }]

      defaults.merge(self)
    end
    alias to_hash to_h

    def update_attributes!(attributes)
      update_attributes(attributes)

      self.class.defaults.each_pair do |prop, value|
        next unless fetch(prop, nil).nil?
        self[prop] = begin
          val = value.dup
          if val.is_a?(Proc)
            val.arity == 1 ? val.call(self) : val.call
          else
            val
          end
        rescue TypeError
          value
        end
      end

      assert_required_attributes_set!
    end

    private

    def initialize_attributes(attributes)
      return unless attributes

      cleaned_attributes = attributes.reject { |_attr, value| value.nil? }
      update_attributes!(cleaned_attributes)
    end

    def update_attributes(attributes)
      return unless attributes

      attributes.each_pair do |att, value|
        self[att] = value
      end
    end

    def assert_property_exists!(property)
      fail_no_property_error!(property) unless self.class.property?(property)
    end

    def assert_required_attributes_set!
      self.class.required_properties.each_key do |required_property|
        assert_property_set!(required_property)
      end
    end

    def assert_property_set!(property)
      fail_property_required_error!(property) if send(property).nil? && required?(property)
    end

    def assert_property_required!(property, value)
      fail_property_required_error!(property) if value.nil? && required?(property)
    end

    def fail_property_required_error!(property)
      raise ArgumentError,
            "The property '#{property}' #{self.class.required_properties[property][:message]}"
    end

    def fail_no_property_error!(property)
      raise NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
    end

    def required?(property)
      return false unless self.class.required?(property)

      condition = self.class.required_properties[property][:condition]
      case condition
      when Proc   then !!instance_exec(&condition)
      when Symbol then !!send(condition)
      else             !!condition
      end
    end
  end
end


def [](property)
      assert_property_exists! property
      value = super(property)
      # If the value is a lambda, proc, or whatever answers to call, eval the thing!
      if value.is_a? Proc
        self[property] = value.call # Set the result of the call as a value
      else
        yield value if block_given?
        value
      end
    end


def update_attributes!(attributes)
      update_attributes(attributes)

      self.class.defaults.each_pair do |prop, value|
        next unless fetch(prop, nil).nil?
        self[prop] = begin
          val = value.dup
          if val.is_a?(Proc)
            val.arity == 1 ? val.call(self) : val.call
          else
            val
          end
        rescue TypeError
          value
        end
      end

      assert_required_attributes_set!
    end


require 'hashie/extensions/stringify_keys'
require 'hashie/extensions/pretty_inspect'

module Hashie
  # A Hashie Hash is simply a Hash that has convenience
  # functions baked in such as stringify_keys that may
  # not be available in all libraries.
  class Hash < ::Hash
    include Hashie::Extensions::PrettyInspect
    include Hashie::Extensions::StringifyKeys

    # Convert this hash into a Mash
    def to_mash
      ::Hashie::Mash.new(self)
    end

    # Converts a mash back to a hash (with stringified or symbolized keys)
    def to_hash(options = {})
      out = {}
      each_key do |k|
        assignment_key =
          if options[:stringify_keys]
            k.to_s
          elsif options[:symbolize_keys] && k.respond_to?(:to_sym)
            k.to_sym
          else
            k
          end
        if self[k].is_a?(Array)
          out[assignment_key] ||= []
          self[k].each do |array_object|
            out[assignment_key] << maybe_convert_to_hash(array_object, options)
          end
        else
          out[assignment_key] = maybe_convert_to_hash(self[k], options)
        end
      end
      out
    end

    # The C generator for the json gem doesn't like mashies
    def to_json(*args)
      to_hash.to_json(*args)
    end

    private

    def maybe_convert_to_hash(object, options)
      return object unless object.is_a?(Hash) || object.respond_to?(:to_hash)

      flexibly_convert_to_hash(object, options)
    end

    def flexibly_convert_to_hash(object, options = {})
      if object.method(:to_hash).arity.zero?
        object.to_hash
      else
        object.to_hash(options)
      end
    end
  end
end


require 'hashie/hash'
require 'hashie/array'
require 'hashie/utils'
require 'hashie/logger'
require 'hashie/extensions/key_conflict_warning'

module Hashie
  # Mash allows you to create pseudo-objects that have method-like
  # accessors for hash keys. This is useful for such implementations
  # as an API-accessing library that wants to fake robust objects
  # without the overhead of actually doing so. Think of it as OpenStruct
  # with some additional goodies.
  #
  # A Mash will look at the methods you pass it and perform operations
  # based on the following rules:
  #
  # * No punctuation: Returns the value of the hash for that key, or nil if none exists.
  # * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
  # * Truthiness (<tt>?</tt>): Returns true or false depending on the truthiness of
  #   the attribute, or false if the key is not set.
  # * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it
  #   as "touch" for mashes.
  # * Under Bang (<tt>_</tt>): Like Bang, but returns a new Mash rather than creating a key.
  #   Used to test existence in deep Mashes.
  #
  # == Basic Example
  #
  #   mash = Mash.new
  #   mash.name? # => false
  #   mash.name = "Bob"
  #   mash.name # => "Bob"
  #   mash.name? # => true
  #
  # == Hash Conversion  Example
  #
  #   hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
  #   mash = Mash.new(hash)
  #   mash.a.b # => 23
  #   mash.a.d.e # => "abc"
  #   mash.f.first.g # => 44
  #   mash.f.last # => 12
  #
  # == Bang Example
  #
  #   mash = Mash.new
  #   mash.author # => nil
  #   mash.author! # => <Mash>
  #
  #   mash = Mash.new
  #   mash.author!.name = "Michael Bleigh"
  #   mash.author # => <Mash name="Michael Bleigh">
  #
  # == Under Bang Example
  #
  #   mash = Mash.new
  #   mash.author # => nil
  #   mash.author_ # => <Mash>
  #   mash.author_.name # => nil
  #
  #   mash = Mash.new
  #   mash.author_.name = "Michael Bleigh"  (assigned to temp object)
  #   mash.author # => <Mash>
  #
  class Mash < Hash
    include Hashie::Extensions::RubyVersionCheck
    extend Hashie::Extensions::KeyConflictWarning

    ALLOWED_SUFFIXES = %w[? ! = _].freeze

    def self.load(path, options = {})
      @_mashes ||= new

      return @_mashes[path] if @_mashes.key?(path)
      raise ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path)

      options = options.dup
      parser = options.delete(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
      @_mashes[path] = new(parser.perform(path, options)).freeze
    end

    def to_module(mash_method_name = :settings)
      mash = self
      Module.new do |m|
        m.send :define_method, mash_method_name.to_sym do
          mash
        end
      end
    end

    def with_accessors!
      extend Hashie::Extensions::Mash::DefineAccessors
    end

    alias to_s inspect

    # If you pass in an existing hash, it will
    # convert it to a Mash including recursively
    # descending into arrays and hashes, converting
    # them as well.
    def initialize(source_hash = nil, default = nil, &blk)
      deep_update(source_hash) if source_hash
      default ? super(default) : super(&blk)
    end

    # Creates a new anonymous subclass with key conflict
    # warnings disabled. You may pass an array of method
    # symbols to restrict the disabled warnings to.
    # Hashie::Mash.quiet.new(hash) all warnings disabled.
    # Hashie::Mash.quiet(:zip).new(hash) only zip warning
    # is disabled.
    def self.quiet(*method_keys)
      @memoized_classes ||= {}
      @memoized_classes[method_keys] ||= Class.new(self) do
        disable_warnings(*method_keys)
      end
    end

    class << self; alias [] new; end

    alias regular_reader []
    alias regular_writer []=

    # Retrieves an attribute set in the Mash. Will convert a key passed in
    # as a symbol to a string before retrieving.
    def custom_reader(key)
      default_proc.call(self, key) if default_proc && !key?(key)
      value = regular_reader(convert_key(key))
      yield value if block_given?
      value
    end

    # Sets an attribute in the Mash. Symbol keys will be converted to
    # strings before being set, and Hashes will be converted into Mashes
    # for nesting purposes.
    def custom_writer(key, value, convert = true) #:nodoc:
      log_built_in_message(key) if key.respond_to?(:to_sym) && log_collision?(key.to_sym)
      regular_writer(convert_key(key), convert ? convert_value(value) : value)
    end

    alias [] custom_reader
    alias []= custom_writer

    # This is the bang method reader, it will return a new Mash
    # if there isn't a value already assigned to the key requested.
    def initializing_reader(key)
      ck = convert_key(key)
      regular_writer(ck, self.class.new) unless key?(ck)
      regular_reader(ck)
    end

    # This is the under bang method reader, it will return a temporary new Mash
    # if there isn't a value already assigned to the key requested.
    def underbang_reader(key)
      ck = convert_key(key)
      if key?(ck)
        regular_reader(ck)
      else
        self.class.new
      end
    end

    def fetch(key, *args)
      super(convert_key(key), *args)
    end

    def delete(key)
      super(convert_key(key))
    end

    def values_at(*keys)
      super(*keys.map { |key| convert_key(key) })
    end

    # Returns a new instance of the class it was called on, using its keys as
    # values, and its values as keys. The new values and keys will always be
    # strings.
    def invert
      self.class.new(super)
    end

    # Returns a new instance of the class it was called on, containing elements
    # for which the given block returns false.
    def reject(&blk)
      self.class.new(super(&blk))
    end

    # Returns a new instance of the class it was called on, containing elements
    # for which the given block returns true.
    def select(&blk)
      self.class.new(super(&blk))
    end

    alias regular_dup dup
    # Duplicates the current mash as a new mash.
    def dup
      self.class.new(self, default, &default_proc)
    end

    alias regular_key? key?
    def key?(key)
      super(convert_key(key))
    end
    alias has_key? key?
    alias include? key?
    alias member? key?

    if with_minimum_ruby?('2.6.0')
      # Performs a deep_update on a duplicate of the
      # current mash.
      def deep_merge(*other_hashes, &blk)
        dup.deep_update(*other_hashes, &blk)
      end

      # Recursively merges this mash with the passed
      # in hash, merging each hash in the hierarchy.
      def deep_update(*other_hashes, &blk)
        other_hashes.each do |other_hash|
          _deep_update(other_hash, &blk)
        end
        self
      end
    else
      # Performs a deep_update on a duplicate of the
      # current mash.
      def deep_merge(other_hash, &blk)
        dup.deep_update(other_hash, &blk)
      end

      # Recursively merges this mash with the passed
      # in hash, merging each hash in the hierarchy.
      def deep_update(other_hash, &blk)
        _deep_update(other_hash, &blk)
        self
      end
    end

    # Alias these lexically so they get the correctly defined
    # #deep_merge and #deep_update based on ruby version.
    alias merge deep_merge
    alias deep_merge! deep_update
    alias update deep_update
    alias merge! update

    def _deep_update(other_hash, &blk)
      other_hash.each_pair do |k, v|
        key = convert_key(k)
        if v.is_a?(::Hash) && key?(key) && regular_reader(key).is_a?(Mash)
          custom_reader(key).deep_update(v, &blk)
        else
          value = convert_value(v, true)
          value = convert_value(yield(key, self[k], value), true) if blk && key?(k)
          custom_writer(key, value, false)
        end
      end
    end
    private :_deep_update

    # Assigns a value to a key
    def assign_property(name, value)
      self[name] = value
    end

    # Performs a shallow_update on a duplicate of the current mash
    def shallow_merge(other_hash)
      dup.shallow_update(other_hash)
    end

    # Merges (non-recursively) the hash from the argument,
    # changing the receiving hash
    def shallow_update(other_hash)
      other_hash.each_pair do |k, v|
        regular_writer(convert_key(k), convert_value(v, true))
      end
      self
    end

    def replace(other_hash)
      (keys - other_hash.keys).each { |key| delete(key) }
      other_hash.each { |key, value| self[key] = value }
      self
    end

    def respond_to_missing?(method_name, *args)
      return true if key?(method_name)
      suffix = method_suffix(method_name)
      if suffix
        true
      else
        super
      end
    end

    def prefix_method?(method_name)
      method_name = method_name.to_s
      method_name.end_with?(*ALLOWED_SUFFIXES) && key?(method_name.chop)
    end

    def method_missing(method_name, *args, &blk) # rubocop:disable Style/MethodMissing
      return self.[](method_name, &blk) if key?(method_name)
      name, suffix = method_name_and_suffix(method_name)
      case suffix
      when '='.freeze
        assign_property(name, args.first)
      when '?'.freeze
        !!self[name]
      when '!'.freeze
        initializing_reader(name)
      when '_'.freeze
        underbang_reader(name)
      else
        self[method_name]
      end
    end

    # play nice with ActiveSupport Array#extract_options!
    def extractable_options?
      true
    end

    # another ActiveSupport method, see issue #270
    def reverse_merge(other_hash)
      self.class.new(other_hash).merge(self)
    end

    def dig(*keys)
      super(*keys.map { |key| convert_key(key) })
    end

    def transform_values(&blk)
      self.class.new(super(&blk))
    end

    # Returns a new instance of the class it was called on, with nil values
    # removed.
    def compact
      self.class.new(super)
    end

    with_minimum_ruby('2.5.0') do
      def slice(*keys)
        string_keys = keys.map { |key| convert_key(key) }
        self.class.new(super(*string_keys))
      end

      def transform_keys(&blk)
        self.class.new(super(&blk))
      end
    end

    with_minimum_ruby('3.0.0') do
      def except(*keys)
        string_keys = keys.map { |key| convert_key(key) }
        self.class.new(super(*string_keys))
      end
    end

    protected

    def method_name_and_suffix(method_name)
      method_name = method_name.to_s
      if method_name.end_with?(*ALLOWED_SUFFIXES)
        [method_name[0..-2], method_name[-1]]
      else
        [method_name[0..-1], nil]
      end
    end

    def method_suffix(method_name)
      method_name = method_name.to_s
      method_name[-1] if method_name.end_with?(*ALLOWED_SUFFIXES)
    end

    def convert_key(key) #:nodoc:
      key.respond_to?(:to_sym) ? key.to_s : key
    end

    def convert_value(val, duping = false) #:nodoc:
      case val
      when self.class
        val.dup
      when Hash
        duping ? val.dup : val
      when ::Hash
        val = val.dup if duping
        self.class.new(val)
      when ::Array
        Array.new(val.map { |e| convert_value(e) })
      else
        val
      end
    end

    private

    def log_built_in_message(method_key)
      return if self.class.disable_warnings?(method_key)

      method_information = Hashie::Utils.method_information(method(method_key))

      Hashie.logger.warn(
        'You are setting a key that conflicts with a built-in method ' \
        "#{self.class}##{method_key} #{method_information}. " \
        'This can cause unexpected behavior when accessing the key as a ' \
        'property. You can still access the key via the #[] method.'
      )
    end

    def log_collision?(method_key)
      return unless method_key.is_a?(String) || method_key.is_a?(Symbol)
      return unless respond_to?(method_key)

      _, suffix = method_name_and_suffix(method_key)

      (!suffix || suffix == '='.freeze) &&
        !self.class.disable_warnings?(method_key) &&
        !(regular_key?(method_key) || regular_key?(method_key.to_s))
    end
  end
end


def convert_value(val, duping = false) #:nodoc:
      case val
      when self.class
        val.dup
      when Hash
        duping ? val.dup : val
      when ::Hash
        val = val.dup if duping
        self.class.new(val)
      when ::Array
        Array.new(val.map { |e| convert_value(e) })
      else
        val
      end
    end


def log_built_in_message(method_key)
      return if self.class.disable_warnings?(method_key)

      method_information = Hashie::Utils.method_information(method(method_key))

      Hashie.logger.warn(
        'You are setting a key that conflicts with a built-in method ' \
        "#{self.class}##{method_key} #{method_information}. " \
        'This can cause unexpected behavior when accessing the key as a ' \
        'property. You can still access the key via the #[] method.'
      )
    end


module Hashie
  #
  # Rash is a Hash whose keys can be Regexps, or Ranges, which will
  # match many input keys.
  #
  # A good use case for this class is routing URLs in a web framework.
  # The Rash's keys match URL patterns, and the values specify actions
  # which can handle the URL. When the Rash's value is proc, the proc
  # will be automatically called with the regexp's matched groups as
  # block arguments.
  #
  # Usage example:
  #
  #     greeting = Hashie::Rash.new( /^Mr./ => "Hello sir!", /^Mrs./ => "Evening, madame." )
  #     greeting["Mr. Steve Austin"] #=> "Hello sir!"
  #     greeting["Mrs. Steve Austin"] #=> "Evening, madame."
  #
  # Note: The Rash is automatically optimized every 500 accesses
  #       (Regexps get sorted by how often they get matched).
  #       If this is too low or too high, you can tune it by
  #       setting: `rash.optimize_every = n`
  #
  class Rash
    attr_accessor :optimize_every

    def initialize(initial = {})
      @hash           = {}
      @regexes        = []
      @ranges         = []
      @regex_counts   = Hash.new(0)
      @optimize_every = 500
      @lookups        = 0

      update(initial)
    end

    def update(other)
      other.each do |key, value|
        self[key] = value
      end

      self
    end

    def []=(key, value)
      case key
      when Regexp
        # key = normalize_regex(key)  # this used to just do: /#{regexp}/
        @regexes << key
      when Range
        @ranges << key
      end
      @hash[key] = value
    end

    #
    # Return the first thing that matches the key.
    #
    def [](key)
      all(key).first
    end

    #
    # Raise (or yield) unless something matches the key.
    #
    def fetch(*args)
      raise ArgumentError, "Expected 1-2 arguments, got #{args.length}" \
        unless (1..2).cover?(args.length)

      key, default = args

      all(key) do |value|
        return value
      end

      if block_given?
        yield key
      elsif default
        default
      else
        raise KeyError, "key not found: #{key.inspect}"
      end
    end

    #
    # Return everything that matches the query.
    #
    def all(query)
      return to_enum(:all, query) unless block_given?

      if @hash.include? query
        yield @hash[query]
        return
      end

      case query
      when String
        optimize_if_necessary!

        # see if any of the regexps match the string
        @regexes.each do |regex|
          match = regex.match(query)
          next unless match
          @regex_counts[regex] += 1
          value = @hash[regex]
          if value.respond_to? :call
            yield value.call(match)
          else
            yield value
          end
        end

      when Numeric
        # see if any of the ranges match the integer
        @ranges.each do |range|
          yield @hash[range] if range.cover? query
        end

      when Regexp
        # Reverse operation: `rash[/regexp/]` returns all string keys matching the regexp
        @hash.each do |key, val|
          yield val if key.is_a?(String) && query =~ key
        end
      end
    end

    def method_missing(*args, &block)
      @hash.send(*args, &block) || super
    end

    def respond_to_missing?(method_name, _include_private = false)
      @hash.respond_to?(method_name)
    end

    private

    def optimize_if_necessary!
      return unless (@lookups += 1) >= @optimize_every
      @regexes = @regexes.sort_by { |regex| -@regex_counts[regex] }
      @lookups = 0
    end
  end
end


def all(query)
      return to_enum(:all, query) unless block_given?

      if @hash.include? query
        yield @hash[query]
        return
      end

      case query
      when String
        optimize_if_necessary!

        # see if any of the regexps match the string
        @regexes.each do |regex|
          match = regex.match(query)
          next unless match
          @regex_counts[regex] += 1
          value = @hash[regex]
          if value.respond_to? :call
            yield value.call(match)
          else
            yield value
          end
        end

      when Numeric
        # see if any of the ranges match the integer
        @ranges.each do |range|
          yield @hash[range] if range.cover? query
        end

      when Regexp
        # Reverse operation: `rash[/regexp/]` returns all string keys matching the regexp
        @hash.each do |key, val|
          yield val if key.is_a?(String) && query =~ key
        end
      end
    end


module Hashie
  class CoercionError < StandardError
    def initialize(key, value, into, message)
      super("Cannot coerce property #{key.inspect} from #{value.class} to #{into}: #{message}")
    end
  end

  module Extensions
    module Coercion
      CORE_TYPES = {
        Integer    => :to_i,
        Float      => :to_f,
        Complex    => :to_c,
        Rational   => :to_r,
        String     => :to_s,
        Symbol     => :to_sym
      }.freeze

      ABSTRACT_CORE_TYPES =
        { Numeric => [Integer, Float, Complex, Rational] }

      def self.included(base)
        base.send :include, InstanceMethods
        base.extend ClassMethods
        unless base.method_defined?(:set_value_without_coercion)
          base.send :alias_method, :set_value_without_coercion, :[]=
        end
        base.send :alias_method, :[]=, :set_value_with_coercion
      end

      module InstanceMethods
        def set_value_with_coercion(key, value)
          into = self.class.key_coercion(key) || self.class.value_coercion(value)

          unless value.nil? || into.nil?
            begin
              value = self.class.fetch_coercion(into).call(value)
            rescue NoMethodError, TypeError => e
              raise CoercionError.new(key, value, into, e.message)
            end
          end

          set_value_without_coercion(key, value)
        end

        def custom_writer(key, value, _convert = true)
          self[key] = value
        end

        def replace(other_hash)
          (keys - other_hash.keys).each { |key| delete(key) }
          other_hash.each { |key, value| self[key] = value }
          self
        end
      end

      module ClassMethods
        attr_writer :key_coercions
        protected :key_coercions=

        # Set up a coercion rule such that any time the specified
        # key is set it will be coerced into the specified class.
        # Coercion will occur by first attempting to call Class.coerce
        # and then by calling Class.new with the value as an argument
        # in either case.
        #
        # @param [Object] key the key or array of keys you would like to be coerced.
        # @param [Class] into the class into which you want the key(s) coerced.
        #
        # @example Coerce a "user" subhash into a User object
        #   class Tweet < Hash
        #     include Hashie::Extensions::Coercion
        #     coerce_key :user, User
        #   end
        def coerce_key(*attrs)
          into = attrs.pop
          attrs.each { |key| key_coercions[key] = into }
        end

        alias coerce_keys coerce_key

        # Returns a hash of any existing key coercions.
        def key_coercions
          @key_coercions ||= {}
        end

        # Returns the specific key coercion for the specified key,
        # if one exists.
        def key_coercion(key)
          key_coercions[key.to_sym]
        end

        # Set up a coercion rule such that any time a value of the
        # specified type is set it will be coerced into the specified
        # class.
        #
        # @param [Class] from the type you would like coerced.
        # @param [Class] into the class into which you would like the value coerced.
        # @option options [Boolean] :strict (true) whether use exact source class
        #   only or include ancestors
        #
        # @example Coerce all hashes into this special type of hash
        #   class SpecialHash < Hash
        #     include Hashie::Extensions::Coercion
        #     coerce_value Hash, SpecialHash
        #
        #     def initialize(hash = {})
        #       super
        #       hash.each_pair do |k,v|
        #         self[k] = v
        #       end
        #     end
        #   end
        def coerce_value(from, into, options = {})
          options = { strict: true }.merge(options)

          if ABSTRACT_CORE_TYPES.key? from
            ABSTRACT_CORE_TYPES[from].each do |type|
              coerce_value type, into, options
            end
          end

          if options[:strict]
            strict_value_coercions[from] = into
          else
            while from.superclass && from.superclass != Object
              lenient_value_coercions[from] = into
              from = from.superclass
            end
          end
        end

        # Return all value coercions that have the :strict rule as true.
        def strict_value_coercions
          @strict_value_coercions ||= {}
        end

        # Return all value coercions that have the :strict rule as false.
        def lenient_value_coercions
          @lenient_value_coercions ||= {}
        end

        # Fetch the value coercion, if any, for the specified object.
        def value_coercion(value)
          from = value.class
          strict_value_coercions[from] || lenient_value_coercions[from]
        end

        def fetch_coercion(type)
          return type if type.is_a? Proc
          coercion_cache[type]
        end

        def coercion_cache
          @coercion_cache ||= ::Hash.new do |hash, type|
            hash[type] = build_coercion(type)
          end
        end

        def build_coercion(type)
          if type.is_a? Enumerable
            if type.class == ::Hash
              type, key_type, value_type = type.class, *type.first
              build_hash_coercion(type, key_type, value_type)
            else
              value_type = type.first
              type = type.class
              build_container_coercion(type, value_type)
            end
          elsif CORE_TYPES.key? type
            build_core_type_coercion(type)
          elsif type.respond_to? :coerce
            lambda do |value|
              return value if value.is_a? type
              type.coerce(value)
            end
          elsif type.respond_to? :new
            lambda do |value|
              return value if value.is_a? type
              type.new(value)
            end
          else
            raise TypeError, "#{type} is not a coercable type"
          end
        end

        def build_hash_coercion(type, key_type, value_type)
          key_coerce = fetch_coercion(key_type)
          value_coerce = fetch_coercion(value_type)
          lambda do |value|
            type[value.map { |k, v| [key_coerce.call(k), value_coerce.call(v)] }]
          end
        end

        def build_container_coercion(type, value_type)
          value_coerce = fetch_coercion(value_type)
          lambda do |value|
            type.new(value.map { |v| value_coerce.call(v) })
          end
        end

        def build_core_type_coercion(type)
          name = CORE_TYPES[type]
          lambda do |value|
            return value if value.is_a? type
            return value.send(name)
          end
        end

        def inherited(klass)
          super

          klass.key_coercions = key_coercions.dup
        end
      end
    end
  end
end


def coerce_value(from, into, options = {})
          options = { strict: true }.merge(options)

          if ABSTRACT_CORE_TYPES.key? from
            ABSTRACT_CORE_TYPES[from].each do |type|
              coerce_value type, into, options
            end
          end

          if options[:strict]
            strict_value_coercions[from] = into
          else
            while from.superclass && from.superclass != Object
              lenient_value_coercions[from] = into
              from = from.superclass
            end
          end
        end


def build_coercion(type)
          if type.is_a? Enumerable
            if type.class == ::Hash
              type, key_type, value_type = type.class, *type.first
              build_hash_coercion(type, key_type, value_type)
            else
              value_type = type.first
              type = type.class
              build_container_coercion(type, value_type)
            end
          elsif CORE_TYPES.key? type
            build_core_type_coercion(type)
          elsif type.respond_to? :coerce
            lambda do |value|
              return value if value.is_a? type
              type.coerce(value)
            end
          elsif type.respond_to? :new
            lambda do |value|
              return value if value.is_a? type
              type.new(value)
            end
          else
            raise TypeError, "#{type} is not a coercable type"
          end
        end


module Hashie
  module Extensions
    # Searches a deeply nested datastructure for a key path, and returns the associated value.
    #
    #  options = { user: { location: { address: '123 Street' } } }
    #  options.deep_fetch :user, :location, :address #=> '123 Street'
    #
    # If a block is provided its value will be returned if the key does not exist.
    #
    #  options.deep_fetch(:user, :non_existent_key) { 'a value' } #=> 'a value'
    #
    # This is particularly useful for fetching values from deeply nested api responses
    #   or params hashes.
    module DeepFetch
      class UndefinedPathError < StandardError; end

      def deep_fetch(*args, &block)
        args.reduce(self) do |obj, arg|
          begin
            arg = Integer(arg) if obj.is_a? Array
            obj.fetch(arg)
          rescue ArgumentError, IndexError, NoMethodError => e
            break yield(arg) if block
            raise UndefinedPathError,
                  "Could not fetch path (#{args.join(' > ')}) at #{arg}", e.backtrace
          end
        end
      end
    end
  end
end


module Hashie
  module Extensions
    # IndifferentAccess gives you the ability to not care
    # whether your hash has string or symbol keys. Made famous
    # in Rails for accessing query and POST parameters, this
    # is a handy tool for making sure your hash has maximum
    # utility.
    #
    # One unique feature of this mixin is that it will recursively
    # inject itself into sub-hash instances without modifying
    # the actual class of the sub-hash.
    #
    # @example
    #   class MyHash < Hash
    #     include Hashie::Extensions::MergeInitializer
    #     include Hashie::Extensions::IndifferentAccess
    #   end
    #
    #   h = MyHash.new(:foo => 'bar', 'baz' => 'blip')
    #   h['foo'] # => 'bar'
    #   h[:foo]  # => 'bar'
    #   h[:baz]  # => 'blip'
    #   h['baz'] # => 'blip'
    #
    module IndifferentAccess
      include Hashie::Extensions::RubyVersionCheck

      # @api private
      def self.convert_key(key)
        key.to_s
      end

      def self.included(base)
        Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(base)

        base.class_eval do
          alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
          alias_method :[]=, :indifferent_writer
          alias_method :store, :indifferent_writer
          %w[default update replace fetch delete key? values_at].each do |m|
            alias_method "regular_#{m}", m unless method_defined?("regular_#{m}")
            alias_method m, "indifferent_#{m}"
          end

          %w[include? member? has_key?].each do |key_alias|
            alias_method key_alias, :indifferent_key?
          end

          class << self
            def [](*)
              super.convert!
            end

            def try_convert(*)
              (hash = super) && self[hash]
            end
          end
        end
      end

      # This will inject indifferent access into an instance of
      # a hash without modifying the actual class. This is what
      # allows IndifferentAccess to spread to sub-hashes.
      def self.inject!(hash)
        (class << hash; self; end).send :include, IndifferentAccess
        hash.convert!
      end

      # Injects indifferent access into a duplicate of the hash
      # provided. See #inject!
      def self.inject(hash)
        inject!(hash.dup)
      end

      def convert_key(key)
        IndifferentAccess.convert_key(key)
      end

      # Iterates through the keys and values, reconverting them to
      # their proper indifferent state. Used when IndifferentAccess
      # is injecting itself into member hashes.
      def convert!
        keys.each do |k| # rubocop:disable Performance/HashEachMethods
          indifferent_writer k, regular_delete(k)
        end
        self
      end

      def indifferent_value(value)
        if hash_lacking_indifference?(value)
          IndifferentAccess.inject!(value)
        elsif value.is_a?(::Array)
          value.replace(value.map { |e| indifferent_value(e) })
        else
          value
        end
      end

      def indifferent_default(key = nil)
        return self[convert_key(key)] if key?(key)
        regular_default(key)
      end

      def indifferent_update(other_hash)
        return regular_update(other_hash) if hash_with_indifference?(other_hash)
        other_hash.each_pair do |k, v|
          self[k] = v
        end
      end

      def indifferent_writer(key, value)
        regular_writer convert_key(key), indifferent_value(value)
      end

      def indifferent_fetch(key, *args, &block)
        regular_fetch convert_key(key), *args, &block
      end

      def indifferent_delete(key)
        regular_delete convert_key(key)
      end

      def indifferent_key?(key)
        regular_key? convert_key(key)
      end

      def indifferent_values_at(*indices)
        indices.map { |i| self[i] }
      end

      def indifferent_access?
        true
      end

      def indifferent_replace(other_hash)
        (keys - other_hash.keys).each { |key| delete(key) }
        other_hash.each { |key, value| self[key] = value }
        self
      end

      def merge(*args)
        result = super
        return IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
        result.convert!
      end

      def merge!(*)
        super.convert!
      end

      def to_hash
        {}.tap do |result|
          each_pair { |key, value| result[key] = value }

          if default_proc
            result.default_proc = default_proc
          else
            result.default = default
          end
        end
      end

      with_minimum_ruby('2.5.0') do
        def slice(*keys)
          string_keys = keys.map { |key| convert_key(key) }
          super(*string_keys)
        end
      end

      with_minimum_ruby('3.0.0') do
        def except(*keys)
          string_keys = keys.map { |key| convert_key(key) }
          super(*string_keys)
        end
      end

      protected

      def hash_lacking_indifference?(other)
        other.is_a?(::Hash) &&
          !(other.respond_to?(:indifferent_access?) &&
            other.indifferent_access?)
      end

      def hash_with_indifference?(other)
        other.is_a?(::Hash) &&
          other.respond_to?(:indifferent_access?) &&
          other.indifferent_access?
      end
    end
  end
end


def to_hash
        {}.tap do |result|
          each_pair { |key, value| result[key] = value }

          if default_proc
            result.default_proc = default_proc
          else
            result.default = default
          end
        end
      end


module Hashie
  module Extensions
    # MethodReader allows you to access keys of the hash
    # via method calls. This gives you an OStruct like way
    # to access your hash's keys. It will recognize keys
    # either as strings or symbols.
    #
    # Note that while nil keys will be returned as nil,
    # undefined keys will raise NoMethodErrors. Also note that
    # #respond_to? has been patched to appropriately recognize
    # key methods.
    #
    # @example
    #   class User < Hash
    #     include Hashie::Extensions::MethodReader
    #   end
    #
    #   user = User.new
    #   user['first_name'] = 'Michael'
    #   user.first_name # => 'Michael'
    #
    #   user[:last_name] = 'Bleigh'
    #   user.last_name # => 'Bleigh'
    #
    #   user[:birthday] = nil
    #   user.birthday # => nil
    #
    #   user.not_declared # => NoMethodError
    module MethodReader
      def respond_to_missing?(name, include_private = false)
        return true if key?(name.to_s) || key?(name.to_sym)
        super
      end

      def method_missing(name, *args)
        if key?(name)
          self[name]
        else
          sname = name.to_s
          if key?(sname)
            self[sname]
          elsif sname[-1] == '?'
            kname = sname[0..-2]
            key?(kname) || key?(kname.to_sym)
          else
            super
          end
        end
      end
    end

    # MethodWriter gives you #key_name= shortcuts for
    # writing to your hash. Keys are written as strings,
    # override #convert_key if you would like to have symbols
    # or something else.
    #
    # Note that MethodWriter also overrides #respond_to such
    # that any #method_name= will respond appropriately as true.
    #
    # @example
    #   class MyHash < Hash
    #     include Hashie::Extensions::MethodWriter
    #   end
    #
    #   h = MyHash.new
    #   h.awesome = 'sauce'
    #   h['awesome'] # => 'sauce'
    #
    module MethodWriter
      def respond_to_missing?(name, include_private = false)
        return true if name.to_s =~ /=$/
        super
      end

      def method_missing(name, *args)
        if args.size == 1 && name.to_s =~ /(.*)=$/
          return self[convert_key(Regexp.last_match[1])] = args.first
        end

        super
      end

      def convert_key(key)
        key.to_s
      end
    end

    # MethodQuery gives you the ability to check for the truthiness
    # of a key via method calls. Note that it will return false if
    # the key is set to a non-truthful value, not if the key isn't
    # set at all. Use #key? for checking if a key has been set.
    #
    # MethodQuery will check against both string and symbol names
    # of the method for existing keys. It also patches #respond_to
    # to appropriately detect the query methods.
    #
    # @example
    #   class MyHash < Hash
    #     include Hashie::Extensions::MethodQuery
    #   end
    #
    #   h = MyHash.new
    #   h['abc'] = 123
    #   h.abc? # => true
    #   h['def'] = nil
    #   h.def? # => false
    #   h.hji? # => NoMethodError
    module MethodQuery
      def respond_to_missing?(name, include_private = false)
        if query_method?(name) && indifferent_key?(key_from_query_method(name))
          true
        else
          super
        end
      end

      def method_missing(name, *args)
        return super unless args.empty?

        if query_method?(name)
          key = key_from_query_method(name)
          if indifferent_key?(key)
            !!(self[key] || self[key.to_sym])
          else
            super
          end
        else
          super
        end
      end

      private

      def indifferent_key?(name)
        name = name.to_s
        key?(name) || key?(name.to_sym)
      end

      def key_from_query_method(query_method)
        query_method.to_s[0..-2]
      end

      def query_method?(name)
        name.to_s.end_with?('?')
      end
    end

    # A macro module that will automatically include MethodReader,
    # MethodWriter, and MethodQuery, giving you the ability to read,
    # write, and query keys in a hash using method call shortcuts.
    module MethodAccess
      def self.included(base)
        [MethodReader, MethodWriter, MethodQuery].each do |mod|
          base.send :include, mod
        end
      end
    end

    # A module shared between MethodOverridingWriter and MethodOverridingInitializer
    # to contained shared logic. This module aids in redefining existing hash methods.
    module RedefineMethod
      protected

      def method?(name)
        methods.map(&:to_s).include?(name)
      end

      def redefine_method(method_name)
        eigenclass = class << self; self; end
        eigenclass.__send__(:alias_method, "__#{method_name}", method_name)
        eigenclass.__send__(:define_method, method_name, -> { self[method_name] })
      end
    end

    # MethodOverridingWriter gives you #key_name= shortcuts for
    # writing to your hash. It allows methods to be overridden by
    # #key_name= shortcuts and aliases those methods with two
    # leading underscores.
    #
    # Keys are written as strings. Override #convert_key if you
    # would like to have symbols or something else.
    #
    # Note that MethodOverridingWriter also overrides
    # #respond_to_missing? such that any #method_name= will respond
    # appropriately as true.
    #
    # @example
    #   class MyHash < Hash
    #     include Hashie::Extensions::MethodOverridingWriter
    #   end
    #
    #   h = MyHash.new
    #   h.awesome = 'sauce'
    #   h['awesome'] # => 'sauce'
    #   h.zip = 'a-dee-doo-dah'
    #   h.zip # => 'a-dee-doo-dah'
    #   h.__zip # => [[['awesome', 'sauce'], ['zip', 'a-dee-doo-dah']]]
    #
    module MethodOverridingWriter
      include RedefineMethod

      def convert_key(key)
        key.to_s
      end

      def method_missing(name, *args)
        if args.size == 1 && name.to_s =~ /(.*)=$/
          key = Regexp.last_match[1]
          redefine_method(key) if method?(key) && !already_overridden?(key)
          return self[convert_key(key)] = args.first
        end

        super
      end

      def respond_to_missing?(name, include_private = false)
        return true if name.to_s.end_with?('=')
        super
      end

      protected

      def already_overridden?(name)
        method?("__#{name}")
      end
    end

    # A macro module that will automatically include MethodReader,
    # MethodOverridingWriter, and MethodQuery, giving you the ability
    # to read, write, and query keys in a hash using method call
    # shortcuts that can override object methods. Any overridden
    # object method is automatically aliased with two leading
    # underscores.
    module MethodAccessWithOverride
      def self.included(base)
        [MethodReader, MethodOverridingWriter,
         MethodQuery, MethodOverridingInitializer].each do |mod|
          base.send :include, mod
        end
      end
    end

    # MethodOverridingInitializer allows you to override default hash
    # methods when passing in values from an existing hash. The overridden
    # methods are aliased with two leading underscores.
    #
    # @example
    #   class MyHash < Hash
    #     include Hashie::Extensions::MethodOverridingInitializer
    #   end
    #
    #   h = MyHash.new(zip: 'a-dee-doo-dah')
    #   h.zip # => 'a-dee-doo-dah'
    #   h.__zip # => [[['zip', 'a-dee-doo-dah']]]
    module MethodOverridingInitializer
      include RedefineMethod

      def initialize(hash = {})
        hash.each do |key, value|
          skey = key.to_s
          redefine_method(skey) if method?(skey)
          self[skey] = value
        end
      end
    end
  end
end


def method_missing(name, *args)
        return super unless args.empty?

        if query_method?(name)
          key = key_from_query_method(name)
          if indifferent_key?(key)
            !!(self[key] || self[key.to_sym])
          else
            super
          end
        else
          super
        end
      end


# Copyright (c) Chad Fowler, Rich Kilmer, Jim Weirich and others.
# Portions copyright (c) Engine Yard and Andre Arko
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# 'Software'), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

module Hashie
  module Extensions
    class RubyVersion
      include Comparable

      attr_accessor :segments

      def initialize(version)
        @segments = split_to_segments(version)
      end

      def <=>(other)
        lhsegments = segments
        rhsegments = other.segments

        lhsize = lhsegments.size
        rhsize = rhsegments.size
        limit  = (lhsize > rhsize ? lhsize : rhsize) - 1

        i = 0

        while i <= limit
          lhs = lhsegments[i] || 0
          rhs = rhsegments[i] || 0
          i += 1

          next      if lhs == rhs
          return -1 if lhs.is_a?(String) && rhs.is_a?(Numeric)
          return  1 if lhs.is_a?(Numeric) && rhs.is_a?(String)

          return lhs <=> rhs
        end

        0
      end

      private

      def split_to_segments(version)
        version.scan(/[0-9]+|[a-z]+/i).map do |segment|
          /^\d+$/ =~ segment ? segment.to_i : segment
        end.freeze
      end
    end
  end
end


module Hashie
  module Extensions
    module StringifyKeys
      # Convert all keys in the hash to strings.
      #
      # @example
      #   test = {:abc => 'def'}
      #   test.stringify_keys!
      #   test # => {'abc' => 'def'}
      def stringify_keys!
        StringifyKeys.stringify_keys!(self)
        self
      end

      # Return a new hash with all keys converted
      # to strings.
      def stringify_keys
        StringifyKeys.stringify_keys(self)
      end

      module ClassMethods
        # Stringify all keys recursively within nested
        # hashes and arrays.
        # @api private
        def stringify_keys_recursively!(object)
          case object
          when self.class
            stringify_keys!(object)
          when ::Array
            object.each do |i|
              stringify_keys_recursively!(i)
            end
          when ::Hash
            stringify_keys!(object)
          end
        end

        # Convert all keys in the hash to strings.
        #
        # @param [::Hash] hash
        # @example
        #   test = {:abc => 'def'}
        #   test.stringify_keys!
        #   test # => {'abc' => 'def'}
        def stringify_keys!(hash)
          hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!)
          hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
            stringify_keys_recursively!(hash[k])
            hash[k.to_s] = hash.delete(k)
          end
          hash
        end

        # Return a copy of hash with all keys converted
        # to strings.
        # @param [::Hash] hash
        def stringify_keys(hash)
          copy = hash.dup
          copy.extend(Hashie::Extensions::StringifyKeys) unless copy.respond_to?(:stringify_keys!)
          copy.tap do |new_hash|
            stringify_keys!(new_hash)
          end
        end
      end

      class << self
        include ClassMethods
      end
    end
  end
end