require "highline/import"
require "set"
require "tabulo"
require "tty-screen"

module GitCurate

  # Regexes for unpacking the output of `git branch -vv`
  BRANCH_NAME_REGEX = /\s+/
  LEADING_STAR_REGEX = /^\* /
  REMOTE_INFO_REGEX = /^[^\s]+\s+[^\s]+\s+\[(.+?)\]/

  Branch = Struct.new("Branch", :raw, :proper, :displayable)

  class Runner

    def initialize(opts)
      @opts = opts
    end

    def run
      if ARGV.length != 0
        puts "This script does not accept any arguments."
        exit
      end

      branches = command_to_a("git branch").reject { |raw_branch| excluded_branch?(raw_branch) }.map do |raw_branch|
        Struct::Branch.new(raw_branch, proper_branch(raw_branch), displayable_branch(raw_branch))
      end

      merged_branches = command_to_a("git branch --merged").to_set
      upstream_branches = get_upstream_branches

      table = Tabulo::Table.new(branches, vertical_rule_character: " ", intersection_character: " ",
        horizontal_rule_character: "-", column_padding: 0, align_header: :left) do |t|

        t.add_column(:branch, header: "Branch") { |branch| branch.displayable }

        t.add_column("Last commit") do |branch|
          `git log -n1 --date=short --format='format:%cd' #{branch.proper}`
        end

        t.add_column("Last author") do |branch|
          `git log -n1 --format='format:%an' #{branch.proper}`
        end

        t.add_column("Last subject") do |branch|
          `git log -n1 --format='format:%s' #{branch.proper}`
        end

        t.add_column("Merged\ninto HEAD?") do |branch|
          merged_branches.include?(branch.proper) ? "Merged" : "Not merged"
        end

        t.add_column("Status vs\nupstream") do |branch|
          upstream_branches.fetch(branch.proper, "No upstream")
        end
      end

      prompt = " Delete? [y/n/done/abort/help] "
      longest_response = "abort"
      prompt_and_response_width =
        if interactive?
          prompt.length + longest_response.length + 1
        else
          0
        end
      table.pack(max_table_width: TTY::Screen.width - prompt_and_response_width)

      branches_to_delete = []

      table.each_with_index do |row, index|
        if interactive?
          case HighLine.ask("#{row}#{prompt}")
          when "y"
            branches_to_delete << proper_branch(row.to_h[:branch])
          when "n", ""
            ;  # do nothing
          when "done"
            puts table.horizontal_rule
            finalize(branches_to_delete)
            exit
          when "abort"
            puts table.horizontal_rule
            puts "\nAborting. No branches deleted."
            exit
          else
            puts table.horizontal_rule
            print_help
            puts table.horizontal_rule unless index == 0
            redo
          end
        else
          puts row
        end
      end
      puts table.horizontal_rule

      finalize(branches_to_delete) if interactive?
    end

    private

    def interactive?
      !@opts[:list]
    end

    def proper_branch(raw_branch)
      raw_branch.lstrip.gsub(/^\*\s*/, '')
    end

    def displayable_branch(raw_branch)
      return raw_branch if interactive?

      current_branch?(raw_branch) ? raw_branch : "  " + raw_branch
    end

    def excluded_branch?(raw_branch)
      interactive? && current_branch?(raw_branch)
    end

    def current_branch?(raw_branch)
      raw_branch =~ /^\s*\*/
    end

    # Returns a Hash containing, as keys, all local branches that have upstream branches,
    # and, as values, a brief description of each branch's status relative to its upstream
    # branch (up to date, or ahead/behind)
    def get_upstream_branches
      command_to_a("git branch -vv").map do |line|
        line.gsub!(LEADING_STAR_REGEX, "")
        branch_name = line.split(BRANCH_NAME_REGEX)[0]
        remote_info = line[REMOTE_INFO_REGEX, 1]
        if remote_info.nil?
          nil
        else
          comparison_raw = remote_info.split(":")
          comparison = if comparison_raw.length < 2
                         "Up to date"
                       else
                         comparison_raw[1].strip.capitalize
                       end
          [branch_name, comparison]
        end
      end.compact.to_h
    end

    def finalize(branches_to_delete)
      if branches_to_delete.size != 0
        puts
        system("git branch -D #{branches_to_delete.join(" ")}")
        puts "\nDone"
      else
        puts "\nNo branches deleted."
      end
    end

    def print_help
      puts <<-EOL
  Simply hit <Enter> to keep this branch and skip to the next one;
  or enter one of the following commands:
    y      -- mark branch for deletion
    n      -- keep branch (equivalent to just <Enter>)
    done   -- delete selected branches and exit session
    abort  -- abort without deleting any branches
    help   -- print this help message
  EOL
    end

    # Runs the passed string command as a system command, gathers any lines of output, stripped of
    # leading and trailing whitespace, and returns them as an array.
    def command_to_a(command)
      `#{command}`.split($/).map(&:strip)
    end

  end

end


module CanCan
  # This class is used internally and should only be called through Ability.
  # it holds the information about a "can" call made on Ability and provides
  # helpful methods to determine permission checking and conditions hash generation.
  class Rule # :nodoc:
    attr_reader :base_behavior, :subjects, :actions, :conditions
    attr_writer :expanded_actions

    # The first argument when initializing is the base_behavior which is a true/false
    # value. True for "can" and false for "cannot". The next two arguments are the action
    # and subject respectively (such as :read, @project). The third argument is a hash
    # of conditions and the last one is the block passed to the "can" call.
    def initialize(base_behavior, action, subject, conditions, block)
      raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
      @match_all = action.nil? && subject.nil?
      @base_behavior = base_behavior
      @actions = [action].flatten
      @subjects = [subject].flatten
      @conditions = conditions || {}
      @block = block
    end

    # Matches both the subject and action, not necessarily the conditions
    def relevant?(action, subject)
      subject = subject.values.first if subject.class == Hash
      @match_all || (matches_action?(action) && matches_subject?(subject))
    end

    # Matches the block or conditions hash
    def matches_conditions?(action, subject, extra_args)
      if @match_all
        call_block_with_all(action, subject, extra_args)
      elsif @block && !subject_class?(subject)
        @block.call(subject, *extra_args)
      elsif @conditions.kind_of?(Hash) && subject.class == Hash
        nested_subject_matches_conditions?(subject)
      elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
        matches_conditions_hash?(subject)
      else
        # Don't stop at "cannot" definitions when there are conditions.
        @conditions.empty? ? true : @base_behavior
      end
    end

    def only_block?
      conditions_empty? && !@block.nil?
    end

    def only_raw_sql?
      @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
    end

    def conditions_empty?
      @conditions == {} || @conditions.nil?
    end

    def unmergeable?
      @conditions.respond_to?(:keys) && @conditions.present? &&
        (!@conditions.keys.first.kind_of? Symbol)
    end

    def associations_hash(conditions = @conditions)
      hash = {}
      conditions.map do |name, value|
        hash[name] = associations_hash(value) if value.kind_of? Hash
      end if conditions.kind_of? Hash
      hash
    end

    def attributes_from_conditions
      attributes = {}
      @conditions.each do |key, value|
        attributes[key] = value unless [Array, Range, Hash].include? value.class
      end if @conditions.kind_of? Hash
      attributes
    end

    private

    def subject_class?(subject)
      klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
      klass == Class || klass == Module
    end

    def matches_action?(action)
      @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
    end

    def matches_subject?(subject)
      @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
    end

    def matches_subject_class?(subject)
      @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
    end

    # Checks if the given subject matches the given conditions hash.
    # This behavior can be overriden by a model adapter by defining two class methods:
    # override_matching_for_conditions?(subject, conditions) and
    # matches_conditions_hash?(subject, conditions)
    def matches_conditions_hash?(subject, conditions = @conditions)
      if conditions.empty?
        true
      else
        if model_adapter(subject).override_conditions_hash_matching? subject, conditions
          model_adapter(subject).matches_conditions_hash? subject, conditions
        else
          conditions.all? do |name, value|
            if model_adapter(subject).override_condition_matching? subject, name, value
              model_adapter(subject).matches_condition? subject, name, value
            else
              attribute = subject.send(name)
              if value.kind_of?(Hash)
                if attribute.kind_of? Array
                  attribute.any? { |element| matches_conditions_hash? element, value }
                else
                  !attribute.nil? && matches_conditions_hash?(attribute, value)
                end
              elsif !value.is_a?(String) && value.kind_of?(Enumerable)
                value.include? attribute
              else
                attribute == value
              end
            end
          end
        end
      end
    end

    def nested_subject_matches_conditions?(subject_hash)
      parent, child = subject_hash.first
      matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
    end

    def call_block_with_all(action, subject, extra_args)
      if subject.class == Class
        @block.call(action, subject, nil, *extra_args)
      else
        @block.call(action, subject.class, subject, *extra_args)
      end
    end

    def model_adapter(subject)
      CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
    end
  end
end


module CanCan

  # This module is designed to be included into an Ability class. This will
  # provide the "can" methods for defining and checking abilities.
  #
  #   class Ability
  #     include CanCan::Ability
  #
  #     def initialize(user)
  #       if user.admin?
  #         can :manage, :all
  #       else
  #         can :read, :all
  #       end
  #     end
  #   end
  #
  module Ability
    # Check if the user has permission to perform a given action on an object.
    #
    #   can? :destroy, @project
    #
    # You can also pass the class instead of an instance (if you don't have one handy).
    #
    #   can? :create, Project
    #
    # Nested resources can be passed through a hash, this way conditions which are
    # dependent upon the association will work when using a class.
    #
    #   can? :create, @category => Project
    #
    # Any additional arguments will be passed into the "can" block definition. This
    # can be used to pass more information about the user's request for example.
    #
    #   can? :create, Project, request.remote_ip
    #
    #   can :create, Project do |project, remote_ip|
    #     # ...
    #   end
    #
    # Not only can you use the can? method in the controller and view (see ControllerAdditions),
    # but you can also call it directly on an ability instance.
    #
    #   ability.can? :destroy, @project
    #
    # This makes testing a user's abilities very easy.
    #
    #   def test "user can only destroy projects which he owns"
    #     user = User.new
    #     ability = Ability.new(user)
    #     assert ability.can?(:destroy, Project.new(:user => user))
    #     assert ability.cannot?(:destroy, Project.new)
    #   end
    #
    # Also see the RSpec Matchers to aid in testing.
    def can?(action, subject, *extra_args)
      match = relevant_rules_for_match(action, subject).detect do |rule|
        rule.matches_conditions?(action, subject, extra_args)
      end
      match ? match.base_behavior : false
    end

    # Convenience method which works the same as "can?" but returns the opposite value.
    #
    #   cannot? :destroy, @project
    #
    def cannot?(*args)
      !can?(*args)
    end

    # Defines which abilities are allowed using two arguments. The first one is the action
    # you're setting the permission for, the second one is the class of object you're setting it on.
    #
    #   can :update, Article
    #
    # You can pass an array for either of these parameters to match any one.
    # Here the user has the ability to update or destroy both articles and comments.
    #
    #   can [:update, :destroy], [Article, Comment]
    #
    # You can pass :all to match any object and :manage to match any action. Here are some examples.
    #
    #   can :manage, :all
    #   can :update, :all
    #   can :manage, Project
    #
    # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
    #
    #   can :read, Project, :active => true, :user_id => user.id
    #
    # See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
    # are also used for initial attributes when building a record in ControllerAdditions#load_resource.
    #
    # If the conditions hash does not give you enough control over defining abilities, you can use a block
    # along with any Ruby code you want.
    #
    #   can :update, Project do |project|
    #     project.groups.include?(user.group)
    #   end
    #
    # If the block returns true then the user has that :update ability for that project, otherwise he
    # will be denied access. The downside to using a block is that it cannot be used to generate
    # conditions for database queries.
    #
    # You can pass custom objects into this "can" method, this is usually done with a symbol
    # and is useful if a class isn't available to define permissions on.
    #
    #   can :read, :stats
    #   can? :read, :stats # => true
    #
    # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
    #
    #   can :update, Project, :priority => 3
    #   can? :update, Project # => true
    #
    # If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
    # block will always be executed. This allows you to override the full behavior if the permissions are
    # defined in an external source such as the database.
    #
    #   can do |action, object_class, object|
    #     # check the database and return true/false
    #   end
    #
    def can(action = nil, subject = nil, conditions = nil, &block)
      rules << Rule.new(true, action, subject, conditions, block)
    end

    # Defines an ability which cannot be done. Accepts the same arguments as "can".
    #
    #   can :read, :all
    #   cannot :read, Comment
    #
    # A block can be passed just like "can", however if the logic is complex it is recommended
    # to use the "can" method.
    #
    #   cannot :read, Product do |product|
    #     product.invisible?
    #   end
    #
    def cannot(action = nil, subject = nil, conditions = nil, &block)
      rules << Rule.new(false, action, subject, conditions, block)
    end

    # Alias one or more actions into another one.
    #
    #   alias_action :update, :destroy, :to => :modify
    #   can :modify, Comment
    #
    # Then :modify permission will apply to both :update and :destroy requests.
    #
    #   can? :update, Comment # => true
    #   can? :destroy, Comment # => true
    #
    # This only works in one direction. Passing the aliased action into the "can?" call
    # will not work because aliases are meant to generate more generic actions.
    #
    #   alias_action :update, :destroy, :to => :modify
    #   can :update, Comment
    #   can? :modify, Comment # => false
    #
    # Unless that exact alias is used.
    #
    #   can :modify, Comment
    #   can? :modify, Comment # => true
    #
    # The following aliases are added by default for conveniently mapping common controller actions.
    #
    #   alias_action :index, :show, :to => :read
    #   alias_action :new, :to => :create
    #   alias_action :edit, :to => :update
    #
    # This way one can use params[:action] in the controller to determine the permission.
    def alias_action(*args)
      target = args.pop[:to]
      validate_target(target)
      aliased_actions[target] ||= []
      aliased_actions[target] += args
    end

    # User shouldn't specify targets with names of real actions or it will cause Seg fault
    def validate_target(target)
      raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
    end

    # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
    def aliased_actions
      @aliased_actions ||= default_alias_actions
    end

    # Removes previously aliased actions including the defaults.
    def clear_aliased_actions
      @aliased_actions = {}
    end

    def model_adapter(model_class, action)
      adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
      adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
    end

    # See ControllerAdditions#authorize! for documentation.
    def authorize!(action, subject, *args)
      message = nil
      if args.last.kind_of?(Hash) && args.last.has_key?(:message)
        message = args.pop[:message]
      end
      if cannot?(action, subject, *args)
        message ||= unauthorized_message(action, subject)
        raise AccessDenied.new(message, action, subject)
      end
      subject
    end

    def unauthorized_message(action, subject)
      keys = unauthorized_message_keys(action, subject)
      variables = {:action => action.to_s}
      variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
      message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
      message.blank? ? nil : message
    end

    def attributes_for(action, subject)
      attributes = {}
      relevant_rules(action, subject).map do |rule|
        attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
      end
      attributes
    end

    def has_block?(action, subject)
      relevant_rules(action, subject).any?(&:only_block?)
    end

    def has_raw_sql?(action, subject)
      relevant_rules(action, subject).any?(&:only_raw_sql?)
    end

    def merge(ability)
      ability.send(:rules).each do |rule|
        rules << rule.dup
      end
      self
    end

    private

    def unauthorized_message_keys(action, subject)
      subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
      [subject, :all].map do |try_subject|
        [aliases_for_action(action), :manage].flatten.map do |try_action|
          :"#{try_action}.#{try_subject}"
        end
      end.flatten
    end

    # Accepts an array of actions and returns an array of actions which match.
    # This should be called before "matches?" and other checking methods since they
    # rely on the actions to be expanded.
    def expand_actions(actions)
      actions.map do |action|
        aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
      end.flatten
    end

    # Given an action, it will try to find all of the actions which are aliased to it.
    # This does the opposite kind of lookup as expand_actions.
    def aliases_for_action(action)
      results = [action]
      aliased_actions.each do |aliased_action, actions|
        results += aliases_for_action(aliased_action) if actions.include? action
      end
      results
    end

    def rules
      @rules ||= []
    end

    # Returns an array of Rule instances which match the action and subject
    # This does not take into consideration any hash conditions or block statements
    def relevant_rules(action, subject)
      rules.reverse.select do |rule|
        rule.expanded_actions = expand_actions(rule.actions)
        rule.relevant? action, subject
      end
    end

    def relevant_rules_for_match(action, subject)
      relevant_rules(action, subject).each do |rule|
        if rule.only_raw_sql?
          raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
        end
      end
    end

    def relevant_rules_for_query(action, subject)
      relevant_rules(action, subject).each do |rule|
        if rule.only_block?
          raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
        end
      end
    end

    def default_alias_actions
      {
        :read => [:index, :show],
        :create => [:new],
        :update => [:edit],
      }
    end
  end
end


module CanCan
  # For use with Inherited Resources
  class InheritedResource < ControllerResource # :nodoc:
    def load_resource_instance
      if parent?
        @controller.send :association_chain
        @controller.instance_variable_get("@#{instance_name}")
      elsif new_actions.include? @params[:action].to_sym
        resource = @controller.send :build_resource
        assign_attributes(resource)
      else
        @controller.send :resource
      end
    end

    def resource_base
      @controller.send :end_of_association_chain
    end
  end
end


module CanCan
  # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
  # This class is used internally, so you do not need to call methods directly on it.
  class ControllerResource # :nodoc:
    def self.add_before_filter(controller_class, method, *args)
      options = args.extract_options!
      resource_name = args.first
      before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
      controller_class.send(before_filter_method, options.slice(:only, :except, :if, :unless)) do |controller|
        controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
      end
    end

    def initialize(controller, *args)
      @controller = controller
      @params = controller.params
      @options = args.extract_options!
      @name = args.first
      raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
      raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
      raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
    end

    def load_and_authorize_resource
      load_resource
      authorize_resource
    end

    def load_resource
      unless skip?(:load)
        if load_instance?
          self.resource_instance ||= load_resource_instance
        elsif load_collection?
          self.collection_instance ||= load_collection
        end
      end
    end

    def authorize_resource
      unless skip?(:authorize)
        @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
      end
    end

    def parent?
      @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
    end

    def skip?(behavior) # This could probably use some refactoring
      options = @controller.class.cancan_skipper[behavior][@name]
      if options.nil?
        false
      elsif options == {}
        true
      elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
        true
      elsif [options[:only]].flatten.include?(@params[:action].to_sym)
        true
      end
    end

    protected

    def load_resource_instance
      if !parent? && new_actions.include?(@params[:action].to_sym)
        build_resource
      elsif id_param || @options[:singleton]
        find_resource
      end
    end

    def load_instance?
      parent? || member_action?
    end

    def load_collection?
      resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
    end

    def load_collection
      resource_base.accessible_by(current_ability, authorization_action)
    end

    def build_resource
      resource = resource_base.new(resource_params || {})
      assign_attributes(resource)
    end

    def assign_attributes(resource)
      resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
      initial_attributes.each do |attr_name, value|
        resource.send("#{attr_name}=", value)
      end
      resource
    end

    def initial_attributes
      current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
        resource_params && resource_params.include?(key)
      end
    end

    def find_resource
      if @options[:singleton] && parent_resource.respond_to?(name)
        parent_resource.send(name)
      else
        if @options[:find_by]
          if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
            resource_base.send("find_by_#{@options[:find_by]}!", id_param)
          elsif resource_base.respond_to? "find_by"
            resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
          else
            resource_base.send(@options[:find_by], id_param)
          end
        else
          adapter.find(resource_base, id_param)
        end
      end
    end

    def adapter
      ModelAdapters::AbstractAdapter.adapter_class(resource_class)
    end

    def authorization_action
      parent? ? :show : @params[:action].to_sym
    end

    def id_param
      if @options[:id_param]
        @params[@options[:id_param]]
      else
        @params[parent? ? :"#{name}_id" : :id]
      end.to_s
    end

    def member_action?
      new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
    end

    # Returns the class used for this resource. This can be overriden by the :class option.
    # If +false+ is passed in it will use the resource name as a symbol in which case it should
    # only be used for authorization, not loading since there's no class to load through.
    def resource_class
      case @options[:class]
      when false  then name.to_sym
      when nil    then namespaced_name.to_s.camelize.constantize
      when String then @options[:class].constantize
      else @options[:class]
      end
    end

    def resource_class_with_parent
      parent_resource ? {parent_resource => resource_class} : resource_class
    end

    def resource_instance=(instance)
      @controller.instance_variable_set("@#{instance_name}", instance)
    end

    def resource_instance
      @controller.instance_variable_get("@#{instance_name}") if load_instance?
    end

    def collection_instance=(instance)
      @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
    end

    def collection_instance
      @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
    end

    # The object that methods (such as "find", "new" or "build") are called on.
    # If the :through option is passed it will go through an association on that instance.
    # If the :shallow option is passed it will use the resource_class if there's no parent
    # If the :singleton option is passed it won't use the association because it needs to be handled later.
    def resource_base
      if @options[:through]
        if parent_resource
          @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
        elsif @options[:shallow]
          resource_class
        else
          raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
        end
      else
        resource_class
      end
    end

    def parent_name
      @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
    end

    # The object to load this resource through.
    def parent_resource
      parent_name && fetch_parent(parent_name)
    end

    def fetch_parent(name)
      if @controller.instance_variable_defined? "@#{name}"
        @controller.instance_variable_get("@#{name}")
      elsif @controller.respond_to?(name, true)
        @controller.send(name)
      end
    end

    def current_ability
      @controller.send(:current_ability)
    end

    def name
      @name || name_from_controller
    end

    def resource_params
      if @options[:class]
        params_key = extract_key(@options[:class])
        return @params[params_key] if @params[params_key]
      end

      resource_params_by_namespaced_name
    end

    def resource_params_by_namespaced_name
      @params[extract_key(namespaced_name)]
    end

    def namespace
      @params[:controller].split(/::|\//)[0..-2]
    end

    def namespaced_name
      [namespace, name.camelize].join('::').singularize.camelize.constantize
    rescue NameError
      name
    end

    def name_from_controller
      @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
    end

    def instance_name
      @options[:instance_name] || name
    end

    def collection_actions
      [:index] + [@options[:collection]].flatten
    end

    def new_actions
      [:new, :create] + [@options[:new]].flatten
    end

    private

    def extract_key(value)
       value.to_s.underscore.gsub('/', '_')
    end
  end
end


require 'stringio'

module FakeFS
  # FakeFS File class inherit StringIO
  class File < StringIO
    MODES = [
      READ_ONLY           = 'r'.freeze,
      READ_WRITE          = 'r+'.freeze,
      WRITE_ONLY          = 'w'.freeze,
      READ_WRITE_TRUNCATE = 'w+'.freeze,
      APPEND_WRITE_ONLY   = 'a'.freeze,
      APPEND_READ_WRITE   = 'a+'.freeze
    ].freeze

    FILE_CREATION_MODES = (MODES - [READ_ONLY, READ_WRITE]).freeze

    MODE_BITMASK = (
      RealFile::RDONLY |
      RealFile::WRONLY |
      RealFile::RDWR |
      RealFile::APPEND |
      RealFile::CREAT |
      RealFile::EXCL |
      RealFile::NONBLOCK |
      RealFile::TRUNC |
      (RealFile.const_defined?(:NOCTTY) ? RealFile::NOCTTY : 0) |
      (RealFile.const_defined?(:SYNC) ? RealFile::SYNC : 0)
    )

    FILE_CREATION_BITMASK = RealFile::CREAT

    def self.extname(path)
      RealFile.extname(path)
    end

    def self.join(*parts)
      RealFile.join(parts)
    end

    def self.path(file)
      RealFile.path(file)
    end

    def self.exist?(path)
      if File.symlink?(path)
        referent = File.expand_path(File.readlink(path), File.dirname(path))
        exist?(referent)
      else
        !FileSystem.find(path).nil?
      end
    end

    class << self
      alias exists? exist?

      # Assume nothing is sticky.
      def sticky?(_path)
        false
      end
    end

    def self.readable?(path)
      return false unless exist? path
      File.lstat(path).readable?
    end

    def self.writable?(path)
      return false unless exist? path
      File.lstat(path).writable?
    end

    def self.mtime(path)
      if exists?(path)
        FileSystem.find(path).mtime
      else
        raise Errno::ENOENT
      end
    end

    def self.ctime(path)
      if exists?(path)
        FileSystem.find(path).ctime
      else
        raise Errno::ENOENT
      end
    end

    def self.atime(path)
      if exists?(path)
        FileSystem.find(path).atime
      else
        raise Errno::ENOENT
      end
    end

    def self.utime(atime, mtime, *paths)
      paths.each do |path|
        if exists?(path)
          FileSystem.find(path).atime = atime
          FileSystem.find(path).mtime = mtime
        else
          raise Errno::ENOENT
        end
      end

      paths.size
    end

    def self.size(path)
      if directory?(path)
        64 + (32 * FileSystem.find(path).entries.size)
      else
        read(path).bytesize
      end
    end

    def self.size?(path)
      size(path) if exists?(path) && !size(path).zero?
    end

    def self.zero?(path)
      exists?(path) && size(path) == 0
    end

    if RUBY_VERSION >= '2.4'
      class << self
        alias empty? zero?
      end
    end

    def self.const_missing(name)
      RealFile.const_get(name)
    end

    def self.directory?(path)
      if path.respond_to? :entry
        path.entry.is_a? FakeDir
      else
        result = FileSystem.find(path)
        result ? result.entry.is_a?(FakeDir) : false
      end
    end

    def self.symlink?(path)
      if path.respond_to? :entry
        path.is_a? FakeSymlink
      else
        FileSystem.find(path).is_a? FakeSymlink
      end
    end

    def self.file?(path)
      if path.respond_to? :entry
        path.entry.is_a? FakeFile
      else
        result = FileSystem.find(path)
        result ? result.entry.is_a?(FakeFile) : false
      end
    end

    def self.ftype(filename)
      File.lstat(filename).ftype
    end

    def self.expand_path(file_name, dir_string = FileSystem.current_dir.to_s)
      RealFile.expand_path(file_name, RealFile.expand_path(dir_string, Dir.pwd))
    end

    def self.basename(*args)
      RealFile.basename(*args)
    end

    def self.dirname(path)
      RealFile.dirname(path)
    end

    def self.readlink(path)
      symlink = FileSystem.find(path)
      symlink.target
    end

    def self.read(path, *args)
      options = args[-1].is_a?(Hash) ? args.pop : {}
      length = args.empty? ? nil : args.shift
      offset = args.empty? ? 0 : args.shift
      file = new(path, options)

      raise Errno::ENOENT unless file.exists?
      raise Errno::EISDIR, path.to_s if directory?(path)

      FileSystem.find(path).atime = Time.now
      file.seek(offset)
      file.read(length)
    end

    def self.readlines(path)
      file = new(path)
      if file.exists?
        FileSystem.find(path).atime = Time.now
        file.readlines
      else
        raise Errno::ENOENT
      end
    end

    def self.foreach(path, *args, &block)
      file = new(path)
      if file.exists?
        FileSystem.find(path).atime = Time.now
        if block_given?
          file.each_line(*args, &block)
        else
          file.each_line(*args)
        end
      else
        raise Errno::ENOENT
      end
    end

    def self.rename(source, dest)
      if directory?(source) && file?(dest)
        raise Errno::ENOTDIR, "#{source} or #{dest}"
      elsif file?(source) && directory?(dest)
        raise Errno::EISDIR, "#{source} or #{dest}"
      elsif !exist?(dirname(dest))
        raise Errno::ENOENT, "#{source} or #{dest}"
      end

      if (target = FileSystem.find(source))
        if target.is_a?(FakeFS::FakeSymlink)
          File.symlink(target.target, dest)
        else
          FileSystem.add(dest, target.entry.clone)
        end

        FileSystem.delete(source)
      else
        raise Errno::ENOENT, "#{source} or #{dest}"
      end

      0
    end

    def self.link(source, dest)
      raise Errno::EPERM, "#{source} or #{dest}" if directory?(source)
      raise Errno::ENOENT, "#{source} or #{dest}" unless exists?(source)
      raise Errno::EEXIST, "#{source} or #{dest}" if exists?(dest)

      source = FileSystem.find(source)
      dest = FileSystem.add(dest, source.entry.clone)
      source.link(dest)

      0
    end

    def self.delete(*file_names)
      file_names.each do |file_name|
        raise Errno::ENOENT, file_name.to_s unless exists?(file_name)

        FileUtils.rm(file_name)
      end

      file_names.size
    end

    class << self
      alias unlink delete
    end

    def self.symlink(source, dest)
      FileUtils.ln_s(source, dest)
    end

    def self.stat(file)
      File::Stat.new(file)
    end

    def self.lstat(file)
      File::Stat.new(file, true)
    end

    def self.split(path)
      RealFile.split(path)
    end

    def self.chmod(new_mode, filename)
      # chmod's mode can either be passed in in absolute mode, or symbolic mode
      # for reference: https://ruby-doc.org/stdlib-2.2.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-chmod
      # if the mode is passed in symbolic mode we must convert it to absolute mode
      is_absolute_mode = new_mode.is_a? Numeric
      unless is_absolute_mode
        current_mode = FileSystem.find(filename).mode
        new_mode = convert_symbolic_chmod_to_absolute(new_mode, current_mode)
      end
      FileSystem.find(filename).mode = 0o100000 + new_mode
    end

    # Not exactly right, returns true if the file is chmod +x for owner. In the
    # context of when you would use fakefs, this is usually what you want.
    def self.executable?(filename)
      file = FileSystem.find(filename)
      return false unless file
      (file.mode - 0o100000) & 0o100 != 0
    end

    def self.chown(owner_int, group_int, filename)
      file = FileSystem.find(filename)

      if owner_int && owner_int != -1
        owner_int.is_a?(Integer) || raise(TypeError, "can't convert String into Integer")
        file.uid = owner_int
      end
      if group_int && group_int != -1
        group_int.is_a?(Integer) || raise(TypeError, "can't convert String into Integer")
        file.gid = group_int
      end
    end

    def self.umask(*args)
      RealFile.umask(*args)
    end

    def self.binread(file, length = nil, offset = 0)
      File.read(file, length, offset, mode: 'rb:ASCII-8BIT')
    end

    def self.fnmatch?(pattern, path, flags = 0)
      RealFile.fnmatch?(pattern, path, flags)
    end

    class << self
      alias fnmatch fnmatch?
    end

    # FakeFS Stat class
    class Stat
      attr_reader :ctime, :mtime, :atime, :mode, :uid, :gid
      attr_reader :birthtime

      def initialize(file, lstat = false)
        raise(Errno::ENOENT, file.to_s) unless File.exist?(file)

        @file      = file
        @fake_file = FileSystem.find(@file)
        @__lstat   = lstat
        @ctime     = @fake_file.ctime
        @mtime     = @fake_file.mtime
        @atime     = @fake_file.atime
        @mode      = @fake_file.mode
        @uid       = @fake_file.uid
        @gid       = @fake_file.gid
        @inode     = @fake_file.inode

        @birthtime =
          if @fake_file.respond_to?(:birthtime)
            @fake_file.birthtime
          else
            @fake_file.ctime
          end
      end

      def symlink?
        File.symlink?(@file)
      end

      def directory?
        File.directory?(@file)
      end

      def file?
        File.file?(@file)
      end

      def ftype
        return 'link' if symlink?
        return 'directory' if directory?
        'file'
      end

      def readable?
        # a file is readable if, and only if, it has the following bits:
        #   4 ( read permission )
        #   5 ( read + execute permission )
        #   6 ( read + write permission )
        #   7 ( read + write + execute permission )
        # for each group we will isolate the wanted numbers ( for owner, world, or group )
        # and see if the third bit is set ( as that is the bit for read )
        read_bit = 4
        check_if_bit_set(read_bit)
      end

      def writable?
        # a file is writable if, and only if, it has the following bits:
        #   2 ( write permission )
        #   3 ( write + execute permission )
        #   6 ( read + write permission )
        #   7 ( read + write + execute permission )
        # for each group we will isolate the wanted numbers ( for owner, world, or group )
        # and see if the second bit is set ( as that is the bit for write )
        write_bit = 2
        check_if_bit_set(write_bit)
      end

      # Assume nothing is sticky.
      def sticky?
        false
      end

      # World_writable and readable are platform dependent
      # usually comparing with S_IROTH defined on compilation (MRI)
      def world_writable?
        0o777
      end

      def world_readable?
        0o777
      end

      def nlink
        @fake_file.links.size
      end

      def size
        if @__lstat && symlink?
          @fake_file.target.size
        else
          File.size(@file)
        end
      end

      def zero?
        size == 0
      end

      def ino
        @inode.inode_num
      end

      include Comparable

      def <=>(other)
        @mtime <=> other.mtime
      end

      private

      def check_if_bit_set(bit)
        # get user's group and user ids
        # NOTE: I am picking `Process` over `Etc` as we use `Process`
        # when instaniating file classes. It may be worth it to ensure
        # our Process/Group detection scheme is robust in all cases
        uid = Process.uid
        gid = Process.gid

        # check if bit set for owner
        owner_bits = (@mode >> 6) & 0o7
        if uid == @uid
          # the user is locked out of the file if they are owner of the file
          # but do not have the bit set at the user level
          return true if owner_bits & bit == bit
          return false
        end

        # check if bit set for group
        group_bits = (@mode >> 3) & 0o7
        if gid == @gid
          # the user is locked out of the file if they are in the group that
          # owns the file but do not have the bit set at the group level
          return true if group_bits & bit == bit
          return false
        end

        # check if bit set for world
        world_bits = @mode & 0o7
        return true if world_bits & bit == bit

        false
      end
    end

    attr_reader :path

    def initialize(path, mode = READ_ONLY, _perm = nil)
      @path = path
      @mode = mode.is_a?(Hash) ? (mode[:mode] || READ_ONLY) : mode
      @file = FileSystem.find(path)
      @autoclose = true

      check_modes!

      file_creation_mode? ? create_missing_file : check_file_existence!

      super(@file.content, @mode)
    end

    def exists?
      true
    end

    def write(str)
      val = super(str)
      @file.mtime = Time.now
      val
    end

    alias tell= pos=
    alias sysread read
    alias syswrite write

    undef_method :closed_read?
    undef_method :closed_write?
    undef_method :length
    undef_method :size
    undef_method :string
    undef_method :string=
    if RUBY_PLATFORM == 'java'
      undef_method :to_channel
      undef_method :to_outputstream
      undef_method :to_inputstream
      # JRuby 9.2.0.0
      undef_method :to_output_stream if respond_to?(:to_output_stream)
      undef_method :to_input_stream if respond_to?(:to_input_stream)
    end

    def is_a?(klass)
      RealFile.allocate.is_a?(klass)
    end

    def string
      gets(nil)
    end

    def ioctl(*)
      raise NotImplementedError
    end

    def read_nonblock
      raise NotImplementedError
    end

    def stat
      self.class.stat(@path)
    end

    def lstat
      self.class.lstat(@path)
    end

    def sysseek(position, whence = SEEK_SET)
      seek(position, whence)
      pos
    end

    alias to_i fileno

    def to_io
      self
    end

    def write_nonblock(*)
      raise NotImplementedError
    end

    def readpartial(*)
      raise NotImplementedError
    end

    def atime
      self.class.atime(@path)
    end

    def ctime
      self.class.ctime(@path)
    end

    def flock(*)
      raise NotImplementedError
    end

    def mtime
      self.class.mtime(@path)
    end

    def chmod(new_mode)
      # chmod's mode can either be passed in in absolute mode, or symbolic mode
      # for reference: https://ruby-doc.org/stdlib-2.2.2/libdoc/fileutils/rdoc/FileUtils.html#method-c-chmod
      # if the mode is passed in symbolic mode we must convert it to absolute mode
      is_absolute_mode = new_mode.is_a? Numeric
      unless is_absolute_mode
        current_mode = @file.mode
        new_mode = convert_symbolic_chmod_to_absolute(new_mode, current_mode)
      end
      @file.mode = 0o100000 + new_mode
    end

    def chown(owner_int, group_int)
      return unless group_int && group_int != -1

      owner_int.is_a?(Integer) || raise(
        TypeError, "can't convert String into Integer"
      )
      @file.uid = owner_int

      group_int.is_a?(Integer) || raise(
        TypeError, "can't convert String into Integer"
      )
      @file.gid = group_int
    end

    def self.realpath(*args)
      RealFile.realpath(*args)
    end

    def binmode?
      raise NotImplementedError
    end

    def close_on_exec=(_bool)
      raise NotImplementedError
    end

    def close_on_exec?
      raise NotImplementedError
    end

    def to_path
      @path
    end

    def self.absolute_path(file_name, dir_name = Dir.getwd)
      RealFile.absolute_path(file_name, dir_name)
    end

    attr_accessor :autoclose

    def autoclose?
      @autoclose ? true : false
    end

    alias fdatasync flush

    def size
      File.size(@path)
    end

    def self.realdirpath(*args)
      RealFile.realdirpath(*args)
    end

    def advise(_advice, _offset = 0, _len = 0); end

    def self.write(filename, contents, offset = nil, open_args = {})
      offset, open_args = nil, offset if offset.is_a?(Hash)
      mode = offset ? 'a' : 'w'
      if open_args.any?
        if open_args[:open_args]
          args = [filename, *open_args[:open_args]]
        else
          mode = open_args[:mode] || mode
          args = [filename, mode, open_args]
        end
      else
        args = [filename, mode]
      end
      if offset
        open(*args) do |f| # rubocop:disable Security/Open
          f.seek(offset)
          f.write(contents)
        end
      else
        open(*args) do |f| # rubocop:disable Security/Open
          f << contents
        end
      end

      contents.length
    end

    def self.birthtime(path)
      if exists?(path)
        FileSystem.find(path).birthtime
      else
        raise Errno::ENOENT
      end
    end

    def birthtime
      self.class.birthtime(@path)
    end

    def read(length = nil, buf = '')
      read_buf = super(length, buf)
      read_buf&.force_encoding('ASCII-8BIT') if binary_mode?
      read_buf
    end

    def self.convert_symbolic_chmod_to_absolute(new_mode, current_mode)
      # mode always must be of form <GROUP1>=<FLAGS>,<GROUP2>=<FLAGS,...
      # e.g.: u=wr,go=x
      chmod_pairs = new_mode.split(',')

      # - duplicating groups is OK ( e.g.: 'ugouuoouu' is valid and is interpretted as 'ugo' )
      # - duplicating modes is OK ( e.g.: 'wwwwwwwww' is interpreted as 'w' )
      # - omitting the right hand side is interpretted as removing all permission
      # ( e.g.: 'ugo=' is really 'chmod 000' )
      # - omitting the left hand side is interpretted as all groups ( e.g.: '=rwx' is really 'ugo=rwx' )
      # - if we list a group more than once, we only apply the rightmost permissions
      # ( e.g.: 'ug=rx,g=x' is really 'u=r,g=x' )
      # - we cannot list any flags that are not 'rwx' ( e.g.: converting 'ug=rwx' to 'ug=7' is invalid )
      # or else an error is raised
      #   - in the example above, the following error is raised: 'invalid `perm' symbol in file mode: 7 (ArgumentError)'
      # - we cannot put in any groups that are not 'ugo' ( e.g.: listing groups as 'uzg=x' is invalid )
      # or else an error is raised
      #   - in the example above, the following error is raised: 'invalid `who' symbol in file mode: z (ArgumentError)'
      valid_groups_to_numeric_vals = { 'u' => 0o100, 'g' => 0o10, 'o' => 0o1 }

      # make sure we preload the current group values.
      # chmod works by calculating new permissions based off of existing permissions
      current_groups_to_vals = { 0o100 => 0o0, 0o10 => 0o0, 0o1 => 0o0 }
      [0o100, 0o10, 0o1].each do |group_num|
        perm_amt = get_perms_for_group(current_mode, group_num)
        current_groups_to_vals[group_num] = perm_amt
      end

      chmod_pairs.each do |pair|
        # see if we are dealing with +/- ( granting or removing permissions ) or = ( assigning permissions )
        # note that it IS valid to mix assignment and granting/revoking perissions ( things like u=wrx,g+x are valid )
        assign_perms = '='
        remove_perms = '-'
        add_perms = '+'
        assignment_mode = nil
        if pair.include? remove_perms
          assignment_mode = remove_perms
        elsif pair.include? add_perms
          assignment_mode = add_perms
        elsif pair.include? assign_perms
          assignment_mode = assign_perms
        end

        # if we can't find a mode, then raise an exception as real `chmod` would
        if assignment_mode.nil?
          raise ArgumentError, "Invalid file mode: #{mode}"
        end
        adding_removing_perms = [add_perms, remove_perms].include?(assignment_mode)

        groups = pair.rpartition(assignment_mode).first
        modes = pair.rpartition(assignment_mode).last

        # get the numeric chmod value associated with the symbolic entry
        chmod_perm_num = calculate_chmod_amt_for_mode modes

        # if we give no groups, then we are giving all groups
        if groups == ''
          if adding_removing_perms
            [0o100, 0o10, 0o1].each do |group_num|
              perm_amt = set_perms_for_group(current_groups_to_vals, group_num, assignment_mode, chmod_perm_num)
              current_groups_to_vals[group_num] = perm_amt
            end
          else
            [0o100, 0o10, 0o1].each do |group_num|
              current_groups_to_vals[group_num] = chmod_perm_num
            end
          end
        else
          # make sure there are no invalid flags in the groups and that we discard duplicates as chmod does
          given_groups = groups.split('')
          given_groups = given_groups.uniq
          given_groups.each do |specific_group|
            # ensure that the group is valid
            unless valid_groups_to_numeric_vals.key? specific_group
              raise ArgumentError, "Invalid `who' symbol in file mode: #{specific_group}"
            end

            # take the current chmod amt from earlier and associate that as the current chmod factor for the group
            # if we are adding or removing groups ( via +/- ) then we must make sure that we adjust
            # the current chmod perm number for the group
            group_num = valid_groups_to_numeric_vals[specific_group]
            adjusted_chmod = chmod_perm_num
            if adding_removing_perms
              adjusted_chmod = set_perms_for_group(current_groups_to_vals, group_num, assignment_mode, chmod_perm_num)
            end
            current_groups_to_vals[group_num] = adjusted_chmod
          end
        end
      end

      # return an octal chmod value for the value
      0o100 * current_groups_to_vals[0o100] + 0o10 * current_groups_to_vals[0o10] + current_groups_to_vals[0o1]
    end

    # return the group mode for group num based off the provided current_file_mode
    def self.get_perms_for_group(current_file_mode, group_num)
      # get the current recorded mode of the group and return it to the caller
      # note we don't shift for 'o' since that is the bottom 3 bits
      # note we multiply by 7 since the group num is 1, and octal represents digits 1-7 and we want all 3 bits
      current_group_mode = current_file_mode & (group_num * 7)
      if group_num == 0o100
        current_group_mode = current_group_mode >> 6
      elsif group_num == 0o10
        current_group_mode = current_group_mode >> 3
      end

      current_group_mode
    end

    # given the current chmod values for a file return the result of adding or removing chmod_perm_num from the
    # requested groups permissions ( so performing <GROUP>+<PERMS> or <GROUP>-<PERMS>
    def self.set_perms_for_group(current_groups_to_vals, group_num, assignment_mode, chmod_perm_num)
      # get the current recorded mode of the group
      current_group_mode = current_groups_to_vals[group_num]

      # now that we have the current value of the group, add or remove bits accordingly
      if assignment_mode == '+'
        current_group_mode | chmod_perm_num
      elsif assignment_mode == '-'
        current_group_mode & ~chmod_perm_num
      else
        raise ArguementError "Unknown assignment mode #{assignment_mode}"
      end
    end

    # given a list of modes [rwx] (a) ensure all modes are valid and (b) return the numeric value
    # associated with the modes
    def self.calculate_chmod_amt_for_mode(modes)
      valid_modes_to_numeric_vals = { 'r' => 0o4, 'w' => 0o2, 'x' => 0o1 }

      # if we give no modes, then we are removing all permission
      chmod_perm_num = 0o0
      if modes != ''
        # make sure there are no invalid flags in the modes and that we discard duplicates as chmod does
        given_modes = modes.split('')
        given_modes = given_modes.uniq
        given_modes.each do |specific_mode|
          # ensure that the mode is valid
          unless valid_modes_to_numeric_vals.key? specific_mode
            raise ArgumentError, "Invalid `perm' symbol in file mode: #{specific_mode}"
          end

          chmod_perm_num += valid_modes_to_numeric_vals[specific_mode]
        end
      end

      chmod_perm_num
    end

    # split the private class method decleration so rubocop doesn't complain the line is too long
    private_class_method :convert_symbolic_chmod_to_absolute, :calculate_chmod_amt_for_mode
    private_class_method :get_perms_for_group, :set_perms_for_group

    private

    def check_modes!
      StringIO.new('', @mode)
    end

    def binary_mode?
      @mode.is_a?(String) && (
        @mode.include?('b') ||
        @mode.include?('binary')
      ) && !@mode.include?('bom')
    end

    def check_file_existence!
      raise Errno::ENOENT, @path.to_s unless @file
    end

    def file_creation_mode?
      mode_in?(FILE_CREATION_MODES) || mode_in_bitmask?(FILE_CREATION_BITMASK)
    end

    def mode_in?(list)
      if @mode.respond_to?(:include?)
        list.any? do |element|
          @mode.include?(element)
        end
      end
    end

    def mode_in_bitmask?(mask)
      (@mode & mask) != 0 if @mode.is_a?(Integer)
    end

    # Create a missing file if the path is valid.
    #
    def create_missing_file
      raise Errno::EISDIR, path.to_s if File.directory?(@path)

      return if File.exist?(@path) # Unnecessary check, probably.
      dirname = RealFile.dirname @path

      unless dirname == '.'
        dir = FileSystem.find dirname

        raise Errno::ENOENT, path.to_s unless dir.is_a? FakeDir
      end

      @file = FileSystem.add(path, FakeFile.new)
    end
  end
end


# FakeFS module
module FakeFS
  #
  # = pathname.rb - From MRI 1.9.2
  #
  # Object-Oriented Pathname Class
  #
  # Author:: Tanaka Akira <akr@m17n.org>
  # Documentation:: Author and Gavin Sinclair
  #
  # For documentation, see class Pathname.
  #
  class Pathname
    # to_path is implemented so Pathname objects are
    # usable with File.open, etc.
    TO_PATH = :to_path

    SAME_PATHS =
      if File::FNM_SYSCASE.nonzero?
        proc { |a, b| a.casecmp(b).zero? }
      else
        proc { |a, b| a == b }
      end

    # :startdoc:

    #
    # Create a Pathname object from the given String (or String-like object).
    # If +path+ contains a NUL character (<tt>\0</tt>),
    # an ArgumentError is raised.
    #
    def initialize(path)
      path = path.__send__(TO_PATH) if path.respond_to? TO_PATH
      @path = path.dup

      if /\0/ =~ @path
        raise ArgumentError, "pathname contains \\0: #{@path.inspect}"
      end

      taint if @path.tainted?
    end

    def freeze
      super
      @path.freeze
      self
    end

    def taint
      super
      @path.taint
      self
    end

    def untaint
      super
      @path.untaint
      self
    end

    #
    # Compare this pathname with +other+.  The comparison is string-based.
    # Be aware that two different paths
    # (<tt>foo.txt</tt> and <tt>./foo.txt</tt>) can refer to the same file.
    #
    def ==(other)
      return false unless other.is_a?(Pathname)
      other.to_s == @path
    end

    alias === ==
    alias eql? ==

    # Provides for comparing pathnames, case-sensitively.
    def <=>(other)
      return nil unless other.is_a?(Pathname)
      @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
    end

    def hash # :nodoc:
      @path.hash
    end

    # Return the path as a String.
    def to_s
      @path.dup
    end

    # to_path is implemented so Pathname objects are usable
    # with File.open, etc.
    alias_method TO_PATH, :to_s

    def inspect # :nodoc:
      "#<#{self.class}:#{@path}>"
    end

    # Return a pathname which is substituted by String#sub.
    def sub(pattern, *rest, &block)
      path =
        if block
          @path.sub(pattern, *rest) do |*args|
            begin
              old = Thread.current[:pathname_sub_matchdata]
              Thread.current[:pathname_sub_matchdata] = $~
              # TODO: rewrite without using eval
              eval(
                '$~ = Thread.current[:pathname_sub_matchdata]',
                block.binding,
                __FILE__,
                __LINE__ - 3
              )
            ensure
              Thread.current[:pathname_sub_matchdata] = old
            end
            yield(*args)
          end
        else
          @path.sub(pattern, *rest)
        end
      self.class.new(path)
    end

    if File::ALT_SEPARATOR
      SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}" \
      "#{Regexp.quote File::SEPARATOR}".freeze
      SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
    else
      SEPARATOR_LIST = (Regexp.quote File::SEPARATOR).to_s.freeze
      SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
    end

    # Return a pathname which the extension of the basename is substituted by
    # repl.
    #
    # If self has no extension part, repl is appended.
    def sub_ext(repl)
      ext = File.extname(@path)
      self.class.new(@path.chomp(ext) + repl)
    end

    # Returns clean pathname of +self+ with consecutive slashes and
    # useless dots removed. The filesystem is not accessed.
    #
    # If +consider_symlink+ is +true+, then a more conservative algorithm
    # is used to avoid breaking symbolic linkages.
    # This may retain more <tt>..</tt> entries than absolutely necessary,
    # but without accessing the filesystem, this can't be avoided.
    # See #realpath.
    #
    def cleanpath(consider_symlink = false)
      if consider_symlink
        cleanpath_conservative
      else
        cleanpath_aggressive
      end
    end

    #
    # Returns the real (absolute) pathname of +self+ in the actual
    # filesystem not containing symlinks or useless dots.
    #
    # All components of the pathname must exist when this method is
    # called.
    #
    def realpath(basedir = nil)
      self.class.new(File.realpath(@path, basedir))
    end

    #
    # Returns the real (absolute) pathname of +self+ in the actual filesystem.
    # The real pathname doesn't contain symlinks or useless dots.
    #
    # The last component of the real pathname can be nonexistent.
    #
    def realdirpath(basedir = nil)
      self.class.new(File.realdirpath(@path, basedir))
    end

    # #parent returns the parent directory.
    #
    # This is same as <tt>self + '..'</tt>.
    def parent
      self + '..'
    end

    # #mountpoint? returns +true+ if <tt>self</tt> points to a mountpoint.
    def mountpoint?
      stat1 = lstat
      begin
        stat2 = parent.lstat
        stat1.dev == stat2.dev && stat1.ino == stat2.ino ||
          stat1.dev != stat2.dev
      rescue Errno::ENOENT
        false
      end
    end

    #
    # #root? is a predicate for root directories.
    # I.e. it returns +true+ if the
    # pathname consists of consecutive slashes.
    #
    # It doesn't access actual filesystem.  So it may return +false+ for some
    # pathnames which points to roots such as <tt>/usr/..</tt>.
    #
    def root?
      chop_basename(@path).nil? && /#{SEPARATOR_PAT}/o =~ @path
    end

    # Predicate method for testing whether a path is absolute.
    # It returns +true+ if the pathname begins with a slash.
    def absolute?
      !relative?
    end

    # The opposite of #absolute?
    def relative?
      path = @path
      while (r = chop_basename(path))
        path, _basename = r
      end
      path == ''
    end

    #
    # Iterates over each component of the path.
    #
    #   Pathname.new("/usr/bin/ruby").each_filename { |filename| ... }
    #     # yields "usr", "bin", and "ruby".
    #
    def each_filename # :yield: filename
      return to_enum(__method__) unless block_given?
      _prefix, names = split_names(@path)
      names.each { |filename| yield filename }
      nil
    end

    # Iterates over and yields a new Pathname object
    # for each element in the given path in descending order.
    #
    #  Pathname.new('/path/to/some/file.rb').descend { |v| p v}
    #     #<Pathname:/>
    #     #<Pathname:/path>
    #     #<Pathname:/path/to>
    #     #<Pathname:/path/to/some>
    #     #<Pathname:/path/to/some/file.rb>
    #
    #  Pathname.new('path/to/some/file.rb').descend { |v| p v}
    #     #<Pathname:path>
    #     #<Pathname:path/to>
    #     #<Pathname:path/to/some>
    #     #<Pathname:path/to/some/file.rb>
    #
    # It doesn't access actual filesystem.
    #
    # This method is available since 1.8.5.
    #
    def descend
      vs = []
      ascend { |v| vs << v }
      vs.reverse_each { |v| yield v }
      nil
    end

    # Iterates over and yields a new Pathname object
    # for each element in the given path in ascending order.
    #
    #  Pathname.new('/path/to/some/file.rb').ascend { |v| p v}
    #     #<Pathname:/path/to/some/file.rb>
    #     #<Pathname:/path/to/some>
    #     #<Pathname:/path/to>
    #     #<Pathname:/path>
    #     #<Pathname:/>
    #
    #  Pathname.new('path/to/some/file.rb').ascend { |v| p v}
    #     #<Pathname:path/to/some/file.rb>
    #     #<Pathname:path/to/some>
    #     #<Pathname:path/to>
    #     #<Pathname:path>
    #
    # It doesn't access actual filesystem.
    #
    # This method is available since 1.8.5.
    #
    def ascend
      path = @path
      yield self
      while (r = chop_basename(path))
        path, _name = r
        break if path.empty?
        yield self.class.new(del_trailing_separator(path))
      end
    end

    #
    # Pathname#+ appends a pathname fragment to this one to produce a new
    # Pathname
    # object.
    #
    #   p1 = Pathname.new("/usr")      # Pathname:/usr
    #   p2 = p1 + "bin/ruby"           # Pathname:/usr/bin/ruby
    #   p3 = p1 + "/etc/passwd"        # Pathname:/etc/passwd
    #
    # This method doesn't access the file system; it is pure string
    # manipulation.
    #
    def +(other)
      other = Pathname.new(other) unless other.is_a?(Pathname)
      Pathname.new(plus(@path, other.to_s))
    end
    alias / +

    #
    # Pathname#join joins pathnames.
    #
    # <tt>path0.join(path1, ..., pathN)</tt> is the same as
    # <tt>path0 + path1 + ... + pathN</tt>.
    #
    def join(*args)
      args.unshift self
      result = args.pop
      result = Pathname.new(result) unless result.is_a?(Pathname)
      return result if result.absolute?
      args.reverse_each do |arg|
        arg = Pathname.new(arg) unless arg.is_a?(Pathname)
        result = arg + result
        return result if result.absolute?
      end
      result
    end

    #
    # Returns the children of the directory (files and subdirectories, not
    # recursive) as an array of Pathname objects.  By default, the returned
    # pathnames will have enough information to access the files.  If you set
    # +with_directory+ to +false+, then the returned
    # pathnames will contain the
    # filename only.
    #
    # For example:
    #   pn = Pathname("/usr/lib/ruby/1.8")
    #   pn.children
    #     # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
    #            Pathname:/usr/lib/ruby/1.8/Env.rb,
    #            Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
    #   pn.children(false)
    #     # -> [ Pathname:English.rb,
    #            Pathname:Env.rb,
    #            Pathname:abbrev.rb, ... ]
    #
    # Note that the result never contain the entries
    # <tt>.</tt> and <tt>..</tt> in
    # the directory because they are not children.
    #
    # This method has existed since 1.8.1.
    #
    def children(with_directory = true)
      with_directory = false if @path == '.'
      result = []
      Dir.foreach(@path) do |e|
        next if ['.', '..'].include?(e)
        result <<
          if with_directory
            self.class.new(File.join(@path, e))
          else
            self.class.new(e)
          end
      end
      result
    end

    # Iterates over the children of the directory
    # (files and subdirectories, not recursive).
    # It yields Pathname object for each child.
    # By default, the yielded pathnames will have enough information to access
    # the files.
    # If you set +with_directory+ to +false+,
    # then the returned pathnames will contain the filename only.
    #
    #   Pathname("/usr/local").each_child { |f| p f }
    #   #=> #<Pathname:/usr/local/share>
    #   #   #<Pathname:/usr/local/bin>
    #   #   #<Pathname:/usr/local/games>
    #   #   #<Pathname:/usr/local/lib>
    #   #   #<Pathname:/usr/local/include>
    #   #   #<Pathname:/usr/local/sbin>
    #   #   #<Pathname:/usr/local/src>
    #   #   #<Pathname:/usr/local/man>
    #
    #   Pathname("/usr/local").each_child(false) { |f| p f }
    #   #=> #<Pathname:share>
    #   #   #<Pathname:bin>
    #   #   #<Pathname:games>
    #   #   #<Pathname:lib>
    #   #   #<Pathname:include>
    #   #   #<Pathname:sbin>
    #   #   #<Pathname:src>
    #   #   #<Pathname:man>
    #
    def each_child(with_directory = true, &block)
      children(with_directory).each(&block)
    end

    #
    # #relative_path_from returns a relative path from the argument to the
    # receiver.  If +self+ is absolute, the argument must be absolute too. If
    # +self+ is relative, the argument must be relative too.
    #
    # #relative_path_from doesn't access the filesystem.
    # It assumes no symlinks.
    #
    # ArgumentError is raised when it cannot find a relative path.
    #
    # This method has existed since 1.8.1.
    #
    def relative_path_from(base_directory)
      dest_directory = cleanpath.to_s
      base_directory = base_directory.cleanpath.to_s
      dest_prefix = dest_directory
      dest_names = []
      while (r = chop_basename(dest_prefix))
        dest_prefix, basename = r
        dest_names.unshift basename if basename != '.'
      end
      base_prefix = base_directory
      base_names = []
      while (r = chop_basename(base_prefix))
        base_prefix, basename = r
        base_names.unshift basename if basename != '.'
      end
      unless SAME_PATHS[dest_prefix, base_prefix]
        raise ArgumentError, "different prefix: #{dest_prefix.inspect} " \
        "and #{base_directory.inspect}"
      end
      while !dest_names.empty? &&
            !base_names.empty? &&
            SAME_PATHS[dest_names.first, base_names.first]
        dest_names.shift
        base_names.shift
      end
      if base_names.include? '..'
        raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
      end
      base_names.fill('..')
      relpath_names = base_names + dest_names
      if relpath_names.empty?
        Pathname.new('.')
      else
        Pathname.new(File.join(*relpath_names))
      end
    end

    private

    # chop_basename(path) -> [pre-basename, basename] or nil
    def chop_basename(path)
      base = File.basename(path)
      if /\A#{SEPARATOR_PAT}?\z/o =~ base
        return nil
      else
        return path[0, path.rindex(base)], base
      end
    end

    # split_names(path) -> prefix, [name, ...]
    def split_names(path)
      names = []
      while (r = chop_basename(path))
        path, basename = r
        names.unshift basename
      end

      [path, names]
    end

    def prepend_prefix(prefix, relpath)
      if relpath.empty?
        File.dirname(prefix)
      elsif /#{SEPARATOR_PAT}/o =~ prefix
        prefix = File.dirname(prefix)
        prefix = File.join(prefix, '') if File.basename(prefix + 'a') != 'a'
        prefix + relpath
      else
        prefix + relpath
      end
    end

    #
    # Clean the path simply by resolving and removing excess
    # "." and ".." entries.
    # Nothing more, nothing less.
    #
    def cleanpath_aggressive
      path = @path
      names = []
      pre = path
      while (r = chop_basename(pre))
        pre, base = r
        case base
        when '.' # rubocop:disable Lint/EmptyWhen
        when '..'
          names.unshift base
        else
          if names[0] == '..'
            names.shift
          else
            names.unshift base
          end
        end
      end
      if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
        names.shift while names[0] == '..'
      end
      self.class.new(prepend_prefix(pre, File.join(*names)))
    end

    # trailing_separator?(path) -> bool
    def trailing_separator?(path)
      if (r = chop_basename(path))
        pre, basename = r
        pre.length + basename.length < path.length
      else
        false
      end
    end

    # add_trailing_separator(path) -> path
    def add_trailing_separator(path)
      if File.basename(path + 'a') == 'a'
        path
      else
        # xxx: Is File.join is appropriate to add separator?
        File.join(path, '')
      end
    end

    def del_trailing_separator(path)
      if (r = chop_basename(path))
        pre, basename = r
        pre + basename
      elsif /#{SEPARATOR_PAT}+\z/o =~ path
        $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
      else
        path
      end
    end

    def cleanpath_conservative
      path = @path
      names = []
      pre = path
      while (r = chop_basename(pre))
        pre, base = r
        names.unshift base if base != '.'
      end
      if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
        names.shift while names[0] == '..'
      end
      if names.empty?
        self.class.new(File.dirname(pre))
      else
        names << '.' if names.last != '..' && File.basename(path) == '.'

        result = prepend_prefix(pre, File.join(*names))
        if /\A(?:\.|\.\.)\z/ !~ names.last && trailing_separator?(path)
          self.class.new(add_trailing_separator(result))
        else
          self.class.new(result)
        end
      end
    end

    def plus(path1, path2) # -> path
      prefix2 = path2
      index_list2 = []
      basename_list2 = []
      while (r2 = chop_basename(prefix2))
        prefix2, basename2 = r2
        index_list2.unshift prefix2.length
        basename_list2.unshift basename2
      end

      return path2 if prefix2 != ''

      prefix1 = path1
      while (r1 = chop_basename(prefix1))
        while !basename_list2.empty? && basename_list2.first == '.'
          index_list2.shift
          basename_list2.shift
        end

        prefix1, basename1 = r1
        next if basename1 == '.'
        if basename1 == '..' ||
           basename_list2.empty? ||
           basename_list2.first != '..'
          prefix1 += basename1
          break
        end
        index_list2.shift
        basename_list2.shift
      end

      r1 = chop_basename(prefix1)
      if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
        while !basename_list2.empty? && basename_list2.first == '..'
          index_list2.shift
          basename_list2.shift
        end
      end

      if !basename_list2.empty?
        suffix2 = path2[index_list2.first..-1]
        r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
      else
        r1 ? prefix1 : File.dirname(prefix1)
      end
    end
  end

  # Pathname class
  class Pathname # * IO *
    #
    # #each_line iterates over the line in the file.
    # It yields a String object for each line.
    #
    # This method has existed since 1.8.1.
    #
    def each_line(*args, &block) # :yield: line
      if block_given?
        File.open(@path, 'r') do |io|
          io.each_line(*args, &block)
        end
      else
        enum_for(:each_line, *args)
      end
    end

    # See <tt>IO.read</tt>. Returns all data from the file,
    # or the first +N+ bytes if specified.
    def read(*args)
      File.read(@path, *args)
    end

    # See <tt>IO.binread</tt>.  Returns all the bytes from the file,
    # or the first +N+ if specified.
    def binread(*args)
      File.binread(@path, *args)
    end

    # See <tt>IO.readlines</tt>.  Returns all the lines from the file.
    def readlines(*args)
      File.readlines(@path, *args)
    end

    # See <tt>IO.sysopen</tt>. Not supported by fakefs.
    def sysopen(*_args)
      raise NotImplementedError, 'sysopen is not supported by fakefs'
    end
  end

  # Pathname class
  class Pathname # * File *
    # See <tt>File.atime</tt>.  Returns last access time.
    def atime
      File.atime(@path)
    end

    # See <tt>File.ctime</tt>.
    # Returns last (directory entry, not file) change time.
    def ctime
      File.ctime(@path)
    end

    # See <tt>File.mtime</tt>.  Returns last modification time.
    def mtime
      File.mtime(@path)
    end

    # See <tt>File.chmod</tt>.  Changes permissions.
    def chmod(mode)
      File.chmod(mode, @path)
    end

    # See <tt>File.lchmod</tt>.
    def lchmod(mode)
      File.lchmod(mode, @path)
    end

    # See <tt>File.chown</tt>.  Change owner and group of file.
    def chown(owner, group)
      File.chown(owner, group, @path)
    end

    # See <tt>File.lchown</tt>.
    def lchown(owner, group)
      File.lchown(owner, group, @path)
    end

    # See <tt>File.fnmatch</tt>.  Return +true+
    # if the receiver matches the given pattern
    def fnmatch(pattern, *args)
      File.fnmatch(pattern, @path, *args)
    end

    # See <tt>File.fnmatch?</tt> (same as #fnmatch).
    def fnmatch?(pattern, *args)
      File.fnmatch?(pattern, @path, *args)
    end

    # See <tt>File.ftype</tt>.  Returns "type" of file ("file", "directory",
    # etc).
    def ftype
      File.ftype(@path)
    end

    # See <tt>File.link</tt>.  Creates a hard link.
    def make_link(old)
      File.link(old, @path)
    end

    # See <tt>File.open</tt>.  Opens the file for reading or writing.
    def open(*args, &block) # :yield: file
      File.open(@path, *args, &block)
    end

    # See <tt>File.readlink</tt>.  Read symbolic link.
    def readlink
      self.class.new(File.readlink(@path))
    end

    # See <tt>File.rename</tt>.  Rename the file.
    def rename(to)
      File.rename(@path, to)
    end

    # See <tt>File.stat</tt>.  Returns a <tt>File::Stat</tt> object.
    def stat
      File.stat(@path)
    end

    # See <tt>File.lstat</tt>.
    def lstat
      File.lstat(@path)
    end

    # See <tt>File.symlink</tt>.  Creates a symbolic link.
    def make_symlink(old)
      File.symlink(old, @path)
    end

    # See <tt>File.truncate</tt>.  Truncate the file to +length+ bytes.
    def truncate(length)
      File.truncate(@path, length)
    end

    # See <tt>File.utime</tt>.  Update the access and modification times.
    def utime(atime, mtime)
      File.utime(atime, mtime, @path)
    end

    # See <tt>File.basename</tt>.  Returns the last component of the path.
    def basename(*args)
      self.class.new(File.basename(@path, *args))
    end

    # See <tt>File.dirname</tt>.  Returns all but the last
    # component of the path.
    def dirname
      self.class.new(File.dirname(@path))
    end

    # See <tt>File.extname</tt>.  Returns the file's extension.
    def extname
      File.extname(@path)
    end

    # See <tt>File.expand_path</tt>.
    def expand_path(*args)
      self.class.new(File.expand_path(@path, *args))
    end

    # See <tt>File.split</tt>.  Returns the #dirname and the #basename in an
    # Array.
    def split
      File.split(@path).map { |f| self.class.new(f) }
    end

    # See <tt>File.write</tt>. Returns the number of bytes written.
    def write(string, *args)
      offset = args[0]
      open_args = args[1]
      File.open(@path, open_args || 'w') do |file|
        file.seek(offset) if offset
        return file.write(string)
      end
    end
  end

  # Pathname class
  class Pathname # * FileTest *
    # See <tt>FileTest.blockdev?</tt>.
    def blockdev?
      FileTest.blockdev?(@path)
    end

    # See <tt>FileTest.chardev?</tt>.
    def chardev?
      FileTest.chardev?(@path)
    end

    # See <tt>FileTest.executable?</tt>.
    def executable?
      FileTest.executable?(@path)
    end

    # See <tt>FileTest.executable_real?</tt>.
    def executable_real?
      FileTest.executable_real?(@path)
    end

    # See <tt>FileTest.exist?</tt>.
    def exist?
      FileTest.exist?(@path)
    end

    # See <tt>FileTest.grpowned?</tt>.
    def grpowned?
      FileTest.grpowned?(@path)
    end

    # See <tt>FileTest.directory?</tt>.
    def directory?
      FileTest.directory?(@path)
    end

    # See <tt>FileTest.file?</tt>.
    def file?
      FileTest.file?(@path)
    end

    # See <tt>FileTest.pipe?</tt>.
    def pipe?
      FileTest.pipe?(@path)
    end

    # See <tt>FileTest.socket?</tt>.
    def socket?
      FileTest.socket?(@path)
    end

    # See <tt>FileTest.owned?</tt>.
    def owned?
      FileTest.owned?(@path)
    end

    # See <tt>FileTest.readable?</tt>.
    def readable?
      FileTest.readable?(@path)
    end

    # See <tt>FileTest.world_readable?</tt>.
    def world_readable?
      FileTest.world_readable?(@path)
    end

    # See <tt>FileTest.readable_real?</tt>.
    def readable_real?
      FileTest.readable_real?(@path)
    end

    # See <tt>FileTest.setuid?</tt>.
    def setuid?
      FileTest.setuid?(@path)
    end

    # See <tt>FileTest.setgid?</tt>.
    def setgid?
      FileTest.setgid?(@path)
    end

    # See <tt>FileTest.size?</tt>.
    def size
      FileTest.size?(@path)
    end

    # See <tt>FileTest.size?</tt>.
    def size?
      FileTest.size?(@path)
    end

    # See <tt>FileTest.sticky?</tt>.
    def sticky?
      FileTest.sticky?(@path)
    end

    # See <tt>FileTest.symlink?</tt>.
    def symlink?
      FileTest.symlink?(@path)
    end

    # See <tt>FileTest.writable?</tt>.
    def writable?
      FileTest.writable?(@path)
    end

    # See <tt>FileTest.world_writable?</tt>.
    def world_writable?
      FileTest.world_writable?(@path)
    end

    # See <tt>FileTest.writable_real?</tt>.
    def writable_real?
      FileTest.writable_real?(@path)
    end

    # See <tt>FileTest.zero?</tt>.
    def zero?
      FileTest.zero?(@path)
    end
  end

  # Pathname class
  class Pathname # * Dir *
    # See <tt>Dir.glob</tt>.  Returns or yields Pathname objects.
    def self.glob(*args) # :yield: pathname
      if block_given?
        Dir.glob(*args) { |f| yield new(f) }
      else
        Dir.glob(*args).map { |f| new(f) }
      end
    end

    # See <tt>Dir.getwd</tt>.  Returns the current working directory
    # as a Pathname.
    def self.getwd
      new(Dir.getwd)
    end

    class << self; alias pwd getwd end

    # Return the entries (files and subdirectories) in the directory, each as
    # a Pathname object.
    def entries
      Dir.entries(@path).map { |f| self.class.new(f) }
    end

    # Iterates over the entries (files and subdirectories) in the directory.
    # It yields a Pathname object for each entry.
    #
    # This method has existed since 1.8.1.
    def each_entry(*) # :yield: pathname
      Dir.foreach(@path) { |f| yield self.class.new(f) }
    end

    # See <tt>Dir.mkdir</tt>.  Create the referenced directory.
    def mkdir(*args)
      Dir.mkdir(@path, *args)
    end

    # See <tt>Dir.rmdir</tt>.  Remove the referenced directory.
    def rmdir
      Dir.rmdir(@path)
    end

    # See <tt>Dir.open</tt>.
    def opendir(&block) # :yield: dir
      Dir.open(@path, &block)
    end
  end

  # Pathname class
  class Pathname # * Find *
    #
    # Pathname#find is an iterator to traverse a directory tree
    # in a depth first manner.
    # It yields a Pathname for each file under "this" directory.
    #
    # Since it is implemented by <tt>find.rb</tt>, <tt>Find.prune</tt>
    # can be used to control the traverse.
    #
    # If +self+ is <tt>.</tt>, yielded pathnames begin with
    # a filename in the current directory, not <tt>./</tt>.
    #
    def find(*) # :yield: pathname
      require 'find'
      if @path == '.'
        Find.find(@path) { |f| yield self.class.new(f.sub(%r{/\A\./}, '')) }
      else
        Find.find(@path) { |f| yield self.class.new(f) }
      end
    end
  end

  # Pathname class
  class Pathname # * FileUtils *
    # See <tt>FileUtils.mkpath</tt>.  Creates a full path, including any
    # intermediate directories that don't yet exist.
    def mkpath
      require 'fileutils'
      FileUtils.mkpath(@path)
      nil
    end

    # See <tt>FileUtils.rm_r</tt>.  Deletes a directory and all beneath it.
    def rmtree
      # The name "rmtree" is borrowed from File::Path of Perl.
      # File::Path provides "mkpath" and "rmtree".
      require 'fileutils'
      FileUtils.rm_r(@path)
      nil
    end
  end

  # Pathname class
  class Pathname # * mixed *
    # Removes a file or directory, using <tt>File.unlink</tt> or
    # <tt>Dir.unlink</tt> as necessary.
    def unlink
      if File.directory? @path
        Dir.unlink @path
      else
        File.unlink @path
      end
    end

    alias delete unlink

    if RUBY_VERSION > '2.4'
      # Checks if a file or directory is empty, using
      # <tt>FileTest.empty?</tt> or <tt>Dir.empty?</tt> as necessary.
      def empty?
        if File.directory? @path
          Dir.empty? @path
        else
          FileTest.empty? @path
        end
      end
    end
  end

  # Pathname class
  class Pathname
    undef =~
  end
end


#encoding: ASCII
require 'barby/barcode'

module Barby

  class Code93 < Barcode1D

    SHIFT_DOLLAR  = "\301" # ($)
    SHIFT_PERCENT = "\302" # (%)
    SHIFT_SLASH   = "\303" # (/)
    SHIFT_PLUS    = "\304" # (+)

    SHIFT_CHARACTERS = [SHIFT_DOLLAR, SHIFT_PERCENT, SHIFT_SLASH, SHIFT_PLUS]

    ENCODINGS = {
      "0" => "100010100", "1" => "101001000",
      "2" => "101000100", "3" => "101000010",
      "4" => "100101000", "5" => "100100100",
      "6" => "100100010", "7" => "101010000",
      "8" => "100010010", "9" => "100001010",
      "A" => "110101000", "B" => "110100100",
      "C" => "110100010", "D" => "110010100",
      "E" => "110010010", "F" => "110001010",
      "G" => "101101000", "H" => "101100100",
      "I" => "101100010", "J" => "100110100",
      "K" => "100011010", "L" => "101011000",
      "M" => "101001100", "N" => "101000110",
      "O" => "100101100", "P" => "100010110",
      "Q" => "110110100", "R" => "110110010",
      "S" => "110101100", "T" => "110100110",
      "U" => "110010110", "V" => "110011010",
      "W" => "101101100", "X" => "101100110",
      "Y" => "100110110", "Z" => "100111010",
      "-" => "100101110", "." => "111010100",
      " " => "111010010", "$" => "111001010",
      "/" => "101101110", "+" => "101110110",
      "%" => "110101110",
      SHIFT_DOLLAR  => "100100110",
      SHIFT_PERCENT => "111011010",
      SHIFT_SLASH   => "111010110",
      SHIFT_PLUS    => "100110010"
    }

    EXTENDED_MAPPING = {
      "\000" => "\302U",    " "    => " ",        "@"  => "\302V", "`"    =>    "\302W",
      "\001" => "\301A",    "!"    => "\303A",    "A"  => "A",     "a"    =>    "\304A",
      "\002" => "\301B",    '"'    => "\303B",    "B"  => "B",     "b"    =>    "\304B",
      "\003" => "\301C",    "#"    => "\303C",    "C"  => "C",     "c"    =>    "\304C",
      "\004" => "\301D",    "$"    => "\303D",    "D"  => "D",     "d"    =>    "\304D",
      "\005" => "\301E",    "%"    => "\303E",    "E"  => "E",     "e"    =>    "\304E",
      "\006" => "\301F",    "&"    => "\303F",    "F"  => "F",     "f"    =>    "\304F",
      "\007" => "\301G",    "'"    => "\303G",    "G"  => "G",     "g"    =>    "\304G",
      "\010" => "\301H",    "("    => "\303H",    "H"  => "H",     "h"    =>    "\304H",
      "\011" => "\301I",    ")"    => "\303I",    "I"  => "I",     "i"    =>    "\304I",
      "\012" => "\301J",    "*"    => "\303J",    "J"  => "J",     "j"    =>    "\304J",
      "\013" => "\301K",    "/"    => "\303K",    "K"  => "K",     "k"    =>    "\304K",
      "\014" => "\301L",    ","    => "\303L",    "L"  => "L",     "l"    =>    "\304L",
      "\015" => "\301M",    "-"    => "-",        "M"  => "M",     "m"    =>    "\304M",
      "\016" => "\301N",    "."    => ".",        "N"  => "N",     "n"    =>    "\304N",
      "\017" => "\301O",    "+"    => "\303O",    "O"  => "O",     "o"    =>    "\304O",
      "\020" => "\301P",    "0"    => "0",        "P"  => "P",     "p"    =>    "\304P",
      "\021" => "\301Q",    "1"    => "1",        "Q"  => "Q",     "q"    =>    "\304Q",
      "\022" => "\301R",    "2"    => "2",        "R"  => "R",     "r"    =>    "\304R",
      "\023" => "\301S",    "3"    => "3",        "S"  => "S",     "s"    =>    "\304S",
      "\024" => "\301T",    "4"    => "4",        "T"  => "T",     "t"    =>    "\304T",
      "\025" => "\301U",    "5"    => "5",        "U"  => "U",     "u"    =>    "\304U",
      "\026" => "\301V",    "6"    => "6",        "V"  => "V",     "v"    =>    "\304V",
      "\027" => "\301W",    "7"    => "7",        "W"  => "W",     "w"    =>    "\304W",
      "\030" => "\301X",    "8"    => "8",        "X"  => "X",     "x"    =>    "\304X",
      "\031" => "\301Y",    "9"    => "9",        "Y"  => "Y",     "y"    =>    "\304Y",
      "\032" => "\301Z",    ":"    => "\303Z",    "Z"  => "Z",     "z"    =>    "\304Z",
      "\033" => "\302A",    ";"    => "\302F",    "["  => "\302K", "{"    =>    "\302P",
      "\034" => "\302B",    "<"    => "\302G",    "\\" => "\302L", "|"    =>    "\302Q",
      "\035" => "\302C",    "="    => "\302H",    "]"  => "\302M", "}"    =>    "\302R",
      "\036" => "\302D",    ">"    => "\302I",    "^"  => "\302N", "~"    =>    "\302S",
      "\037" => "\302E",    "?"    => "\302J",    "_"  => "\302O", "\177" =>    "\302T"
    }

    EXTENDED_CHARACTERS = EXTENDED_MAPPING.keys - ENCODINGS.keys

    CHARACTERS = {
      0  => "0",  1 => "1", 2  => "2", 3  => "3",
      4  => "4",  5 => "5", 6  => "6", 7  => "7",
      8  => "8",  9 => "9", 10 => "A", 11 => "B",
      12 => "C", 13 => "D", 14 => "E", 15 => "F",
      16 => "G", 17 => "H", 18 => "I", 19 => "J",
      20 => "K", 21 => "L", 22 => "M", 23 => "N",
      24 => "O", 25 => "P", 26 => "Q", 27 => "R",
      28 => "S", 29 => "T", 30 => "U", 31 => "V",
      32 => "W", 33 => "X", 34 => "Y", 35 => "Z",
      36 => "-", 37 => ".", 38 => " ", 39 => "$",
      40 => "/", 41 => "+", 42 => "%",
      43 => SHIFT_DOLLAR, 44 => SHIFT_PERCENT,
      45 => SHIFT_SLASH,  46 => SHIFT_PLUS
    }

    VALUES = CHARACTERS.invert

    START_ENCODING     = '101011110' # *
    STOP_ENCODING      = '101011110'
    TERMINATE_ENCODING = '1'

    attr_accessor :data

    def initialize(data)
      self.data = data
    end

    def extended?
      raw_characters.any?{|c| EXTENDED_CHARACTERS.include?(c) }
    end

    def raw_characters
      data.split(//)
    end

    def characters
      raw_characters.map{|c| EXTENDED_MAPPING[c].split(//) }.flatten
    end

    def encoded_characters
      characters.map{|c| ENCODINGS[c] }
    end
    alias character_encodings encoded_characters

    def start_encoding
      START_ENCODING
    end

    #The stop encoding includes the termination bar
    def stop_encoding
      STOP_ENCODING+TERMINATE_ENCODING
    end

    def encoding
      start_encoding+data_encoding_with_checksums+stop_encoding
    end

    def data_encoding
      character_encodings.join
    end

    def data_encoding_with_checksums
      (character_encodings + checksum_encodings).join
    end

    def checksum_encodings
      checksum_characters.map{|c| ENCODINGS[c] }
    end

    def checksum_encoding
      checksum_encodings.join
    end

    def checksums
      [c_checksum, k_checksum]
    end

    def checksum_characters
      checksums.map{|s| CHARACTERS[s] }
    end

    #Returns the values used for computing the C checksum
    #in the "right" order (i.e. reversed)
    def checksum_values
      characters.map{|c| VALUES[c] }.reverse
    end

    #Returns the normal checksum plus the c_checksum
    #This is used for calculating the k_checksum
    def checksum_values_with_c_checksum
      [c_checksum] + checksum_values
    end

    #Calculates the C checksum based on checksum_values
    def c_checksum
      sum = 0
      checksum_values.each_with_index do |value, index|
        sum += ((index % 20) + 1) * value
      end
      sum % 47
    end

    def c_checksum_character
      CHARACTERS[c_checksum]
    end

    def c_checksum_encoding
      ENCODINGS[c_checksum_character]
    end

    #Calculates the K checksum based on checksum_values_with_c_checksum
    def k_checksum
      sum = 0
      checksum_values_with_c_checksum.each_with_index do |value, index|
        sum += ((index % 15) + 1) * value
      end
      sum % 47
    end

    def k_checksum_character
      CHARACTERS[k_checksum]
    end

    def k_checksum_encoding
      ENCODINGS[k_checksum_character]
    end

    def valid?
      characters.all?{|c| ENCODINGS.include?(c) }
    end

    def to_s
      data
    end

  end

end


require 'barby/outputter'
require 'rmagick'

module Barby

  #Renders images from barcodes using RMagick
  #
  #Registers the to_png, to_gif, to_jpg and to_image methods
  class RmagickOutputter < Outputter

    register :to_png, :to_gif, :to_jpg, :to_image

    attr_writer :height, :xdim, :ydim, :margin

    def initialize(*)
      super
      @height, @xdim, @ydim, @margin = nil
    end

    #Returns a string containing a PNG image
    def to_png(*a)
      to_blob('png', *a)
    end

    #Returns a string containint a GIF image
    def to_gif(*a)
      to_blob('gif', *a)
    end

    #Returns a string containing a JPEG image
    def to_jpg(*a)
      to_blob('jpg', *a)
    end

    def to_blob(format, *a)
      img = to_image(*a)
      blob = img.to_blob{|i| i.format = format }

      #Release the memory used by RMagick explicitly. Ruby's GC
      #isn't aware of it and can't clean it up automatically
      img.destroy! if img.respond_to?(:destroy!)

      blob
    end

    #Returns an instance of Magick::Image
    def to_image(opts={})
      with_options opts do
        canvas = Magick::Image.new(full_width, full_height)
        bars = Magick::Draw.new

        x1 = margin
        y1 = margin

        if barcode.two_dimensional?
          encoding.each do |line|
            line.split(//).map{|c| c == '1' }.each do |bar|
              if bar
                x2 = x1+(xdim-1)
                y2 = y1+(ydim-1)
                # For single pixels use point
                if x1 == x2 && y1 == y2
                  bars.point(x1,y1)
                else
                  bars.rectangle(x1, y1, x2, y2)
                end
              end
              x1 += xdim
            end
            x1 = margin
            y1 += ydim
          end
        else
          booleans.each do |bar|
            if bar
              x2 = x1+(xdim-1)
              y2 = y1+(height-1)
              bars.rectangle(x1, y1, x2, y2)
            end
            x1 += xdim
          end
        end

        bars.draw(canvas)

        canvas
      end
    end

    #The height of the barcode in px
    #For 2D barcodes this is the number of "lines" * ydim
    def height
      barcode.two_dimensional? ? (ydim * encoding.length) : (@height || 100)
    end

    #The width of the barcode in px
    def width
      length * xdim
    end

    #Number of modules (xdims) on the x axis
    def length
      barcode.two_dimensional? ? encoding.first.length : encoding.length
    end

    #X dimension. 1X == 1px
    def xdim
      @xdim || 1
    end

    #Y dimension. Only for 2D codes
    def ydim
      @ydim || xdim
    end

    #The margin of each edge surrounding the barcode in pixels
    def margin
      @margin || 10
    end

    #The full width of the image. This is the width of the
    #barcode + the left and right margin
    def full_width
      width + (margin * 2)
    end

    #The height of the image. This is the height of the
    #barcode + the top and bottom margin
    def full_height
      height + (margin * 2)
    end

  end

end


require 'barby/outputter'

module Barby

  #Annotates a PDFWriter document with the barcode
  #
  #Registers the annotate_pdf method
  class PDFWriterOutputter < Outputter

    register :annotate_pdf

    attr_accessor :x, :y, :height, :xdim

    #Annotate a PDFWriter document with the barcode
    #
    #Valid options are:
    #
    #x, y   - The point in the document to start rendering from
    #height - The height of the bars in PDF units
    #xdim   - The X dimension in PDF units
    def annotate_pdf(pdf, options={})
      with_options options do

        xpos, ypos = x, y
        orig_xpos = xpos

        if barcode.two_dimensional?
          boolean_groups.reverse_each do |groups|
            groups.each do |bar,amount|
              if bar
                pdf.move_to(xpos, ypos).
                  line_to(xpos, ypos+xdim).
                  line_to(xpos+(xdim*amount), ypos+xdim).
                  line_to(xpos+(xdim*amount), ypos).
                  line_to(xpos, ypos).
                  fill
              end
              xpos += (xdim*amount)
            end
            xpos = orig_xpos
            ypos += xdim
          end
        else
          boolean_groups.each do |bar,amount|
            if bar
              pdf.move_to(xpos, ypos).
                line_to(xpos, ypos+height).
                line_to(xpos+(xdim*amount), ypos+height).
                line_to(xpos+(xdim*amount), ypos).
                line_to(xpos, ypos).
                fill
            end
            xpos += (xdim*amount)
          end
        end

      end

      pdf
    end

    def x
      @x || 10
    end

    def y
      @y || 10
    end

    def height
      @height || 50
    end

    def xdim
      @xdim || 1
    end

  end

end


require 'barby/barcode'

module Barby

  #An Outputter creates something from a barcode. That something can be
  #anything, but is most likely a graphical representation of the barcode.
  #Outputters can register methods on barcodes that will be associated with
  #them.
  #
  #The basic structure of an outputter class:
  #
  #  class FooOutputter < Barby::Outputter
  #    register :to_foo
  #    def to_too
  #      do_something_with(barcode.encoding)
  #    end
  #  end
  #
  #Barcode#to_foo will now be available to all barcodes
  class Outputter

    attr_accessor :barcode

    #An outputter instance will have access to a Barcode
    def initialize(barcode)
      self.barcode = barcode
    end

    #Register one or more handler methods with this outputter
    #Barcodes will then be able to use these methods to get the output
    #from the outputter. For example, if you have an ImageOutputter,
    #you could do:
    #
    #register :to_png, :to_gif
    #
    #You could then do aBarcode.to_png and get the result of that method.
    #The class which registers the method will receive the barcode as the only
    #argument, and the default implementation of initialize puts that into
    #the +barcode+ accessor.
    #
    #You can also have different method names on the barcode and the outputter
    #by providing a hash:
    #
    #register :to_png => :create_png, :to_gif => :create_gif
    def self.register(*method_names)
      if method_names.first.is_a? Hash
        method_names.first.each do |name, method_name|
          Barcode.register_outputter(name, self, method_name)
        end
      else
        method_names.each do |name|
          Barcode.register_outputter(name, self, name)
        end
      end
    end

    def two_dimensional?
      barcode.respond_to?(:two_dimensional?) && barcode.two_dimensional?
    end

    #Converts the barcode's encoding (a string containing 1s and 0s)
    #to true and false values (1 == true == "black bar")
    #
    #If the barcode is 2D, each line will be converted to an array
    #in the same way
    def booleans(reload=false)#:doc:
      if two_dimensional?
        encoding(reload).map{|l| l.split(//).map{|c| c == '1' } }
      else
        encoding(reload).split(//).map{|c| c == '1' }
      end
    end

    #Returns the barcode's encoding. The encoding
    #is cached and can be reloaded by passing true
    def encoding(reload=false)#:doc:
      @encoding = barcode.encoding if reload
      @encoding ||= barcode.encoding
    end

    #Collects continuous groups of bars and spaces (1 and 0)
    #into arrays where the first item is true or false (1 or 0)
    #and the second is the size of the group
    #
    #For example, "1100111000" becomes [[true,2],[false,2],[true,3],[false,3]]
    def boolean_groups(reload=false)
      if two_dimensional?
        encoding(reload).map do |line|
          line.scan(/1+|0+/).map do |group|
            [group[0,1] == '1', group.size]
          end
        end
      else
        encoding(reload).scan(/1+|0+/).map do |group|
          [group[0,1] == '1', group.size]
        end
      end
    end

  private

    #Takes a hash and temporarily sets properties on self (the outputter object)
    #corresponding with the keys to their values. When the block exits, the
    #properties are reset to their original values. Returns whatever the block returns.
    def with_options(options={})
      original_options = options.inject({}) do |origs,pair|
        if respond_to?(pair.first) && respond_to?("#{pair.first}=")
          origs[pair.first] = send(pair.first)
          send("#{pair.first}=", pair.last)
        end
        origs
      end

      rv = yield

      original_options.each do |attribute,value|
        send("#{attribute}=", value)
      end

      rv
    end

  end

end