module Jekyll
  class EntryFilter
    attr_reader :site
    SPECIAL_LEADING_CHARACTERS = [
      ".", "_", "#", "~"
    ].freeze

    def initialize(site, base_directory = nil)
      @site = site
      @base_directory = derive_base_directory(
        @site, base_directory.to_s.dup
      )
    end

    def base_directory
      @base_directory.to_s
    end

    def derive_base_directory(site, base_dir)
      base_dir[site.source] = "" if base_dir.start_with?(site.source)
      base_dir
    end

    def relative_to_source(entry)
      File.join(
        base_directory, entry
      )
    end

    def filter(entries)
      entries.reject do |e|
        unless included?(e)
          special?(e) || backup?(e) || excluded?(e) || symlink?(e)
        end
      end
    end

    def included?(entry)
      glob_include?(site.include,
        entry)
    end

    def special?(entry)
      SPECIAL_LEADING_CHARACTERS.include?(entry[0..0]) ||
        SPECIAL_LEADING_CHARACTERS.include?(File.basename(entry)[0..0])
    end

    def backup?(entry)
      entry[-1..-1] == "~"
    end

    def excluded?(entry)
      excluded = glob_include?(site.exclude, relative_to_source(entry))
      if excluded
        Jekyll.logger.debug(
          "EntryFilter:",
          "excluded #{relative_to_source(entry)}"
        )
      end
      excluded
    end

    # --
    # Check if a file is a symlink.
    # NOTE: This can be converted to allowing even in safe,
    #   since we use Pathutil#in_path? now.
    # --
    def symlink?(entry)
      site.safe && File.symlink?(entry) && symlink_outside_site_source?(entry)
    end

    # --
    # NOTE: Pathutil#in_path? gets the realpath.
    # @param [<Anything>] entry the entry you want to validate.
    # Check if a path is outside of our given root.
    # --
    def symlink_outside_site_source?(entry)
      !Pathutil.new(entry).in_path?(
        site.in_source_dir
      )
    end

    # --
    # Check if an entry matches a specific pattern and return true,false.
    # Returns true if path matches against any glob pattern.
    # --
    def glob_include?(enum, e)
      entry = Pathutil.new(site.in_source_dir).join(e)
      enum.any? do |exp|
        # Users who send a Regexp knows what they want to
        # exclude, so let them send a Regexp to exclude files,
        # we will not bother caring if it works or not, it's
        # on them at this point.

        if exp.is_a?(Regexp)
          entry =~ exp

        else
          item = Pathutil.new(site.in_source_dir).join(exp)

          # If it's a directory they want to exclude, AKA
          # ends with a "/" then we will go on to check and
          # see if the entry falls within that path and
          # exclude it if that's the case.

          if e.end_with?("/")
            entry.in_path?(
              item
            )

          else
            File.fnmatch?(item, entry) ||
              entry.to_path.start_with?(
                item
              )
          end
        end
      end
    end
  end
end
