glebm/i18n-tasks

View on GitHub
lib/i18n/tasks/scanners/files/file_finder.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
# frozen_string_literal: true

module I18n::Tasks::Scanners::Files
  # Finds the files in the specified search paths with support for exclusion / inclusion patterns.
  #
  # @since 0.9.0
  class FileFinder
    include I18n::Tasks::Logging

    # @param paths [Array<String>] {Find.find}-compatible paths to traverse,
    #     absolute or relative to the working directory.
    # @param only [Array<String>, nil] {File.fnmatch}-compatible patterns files to include.
    #     Files not matching any of the inclusion patterns will be excluded.
    # @param exclude [Arry<String>] {File.fnmatch}-compatible patterns of files to exclude.
    #     Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern.
    def initialize(paths: ['.'], only: nil, exclude: [])
      fail 'paths argument is required' if paths.nil?

      @paths   = paths
      @include = only
      @exclude = exclude || []
    end

    # Traverse the paths and yield the matching ones.
    #
    # @yield [path]
    # @yieldparam path [String] the path of the found file.
    # @return [Array<of block results>]
    def traverse_files(&block)
      find_files.map(&block)
    end

    # @return [Array<String>] found files
    def find_files # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
      results = []
      paths = @paths.select { |p| File.exist?(p) }
      log_warn "None of the search.paths exist #{@paths.inspect}" if paths.empty?
      Find.find(*paths) do |path|
        is_dir   = File.directory?(path)
        hidden   = File.basename(path).start_with?('.') && !%w[. ./].include?(path)
        not_incl = @include && !path_fnmatch_any?(path, @include)
        excl     = path_fnmatch_any?(path, @exclude)
        if is_dir || hidden || not_incl || excl
          Find.prune if is_dir && (hidden || excl)
        else
          results << path
        end
      end
      results
    end

    private

    # @param path [String]
    # @param globs [Array<String>]
    # @return [Boolean]
    def path_fnmatch_any?(path, globs)
      globs.any? { |glob| File.fnmatch(glob, path) }
    end
  end
end