mrackwitz/CLIntegracon

View on GitHub
lib/CLIntegracon/file_tree_spec_context.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'CLIntegracon/file_tree_spec'

module CLIntegracon
  class FileTreeSpecContext

    #-----------------------------------------------------------------------------#

    # @!group Attributes

    # @return [Pathname]
    #         The relative path to the integration specs
    attr_accessor :spec_path

    # @return [Pathname]
    #         The relative path from a concrete spec directory to the directory containing the input files,
    #         which will be available at execution
    attr_accessor :before_dir

    # @return [Pathname]
    #         The relative path from a concrete spec directory to the directory containing the expected files after
    #         the execution
    attr_accessor :after_dir

    # @return [Pathname]
    #         The relative path to the directory containing the produced files after the
    #         execution. This must not be the same as the before_dir or the after_dir.
    #
    # @note   **Attention**: This path will been deleted before running to ensure a clean sandbox for testing.
    #
    attr_accessor :temp_path

    # @return [Hash<String|Regexp,Block>]
    #         the paths of files, which need to be transformed in a better comparable form
    attr_accessor :transform_paths

    # @return [Hash<String|Regexp,Block>]
    #         the paths of files, where an individual file diff handling is needed
    attr_accessor :preprocess_paths

    # @return [Array<String|Regexp>]
    #         the paths of files to exclude from comparison
    attr_accessor :ignore_paths

    # @return [Bool]
    #         whether to include hidden files, when searching directories (true by default)
    attr_accessor :include_hidden_files
    alias :include_hidden_files? :include_hidden_files


    #-----------------------------------------------------------------------------#

    # @!group Initializer

    # "Designated" initializer
    #
    # @param  [Hash<Symbol,String>] properties
    #         The configuration parameter (optional):
    #         :spec_path   => see self.spec_path
    #         :before_dir  => see self.before_dir
    #         :after_dir   => see self.after_dir
    #         :temp_path   => see self.temp_path
    #
    def initialize(properties={})
      self.spec_path   = properties[:spec_path]   || '.'
      self.temp_path   = properties[:temp_path]   || 'tmp'
      self.before_dir  = properties[:before_dir]  || 'before'
      self.after_dir   = properties[:after_dir]   || 'after'
      self.transform_paths = {}
      self.preprocess_paths = {}
      self.ignore_paths = []
      self.include_hidden_files = true
    end


    #-----------------------------------------------------------------------------#

    # @!group Setter

    def spec_path=(spec_path)
      # Spec dir has to exist.
      @spec_path= Pathname(spec_path).realpath
    end

    def temp_path=(temp_path)
      # Temp dir, doesn't have to exist itself, it will been created, but let's ensure
      # that at least the last but one path component exist.
      raise "temp_path's parent directory doesn't exist" unless (Pathname(temp_path) + '..').exist?
      @temp_path = Pathname(temp_path)
    end

    def before_dir=(before_dir)
      @before_dir = Pathname(before_dir)
    end

    def after_dir=(after_dir)
      @after_dir = Pathname(after_dir)
    end


    #-----------------------------------------------------------------------------#

    # @!group DSL-like Setter

    # Registers a block to transform certain files, matched with globs or
    # regular expressions.
    # Multiple transformers can match a single file.
    #
    # @param  [String...] file_paths
    #         The path(s), which need to be transformed in a better comparable form
    #
    # @param  [Block<(Pathname) -> ()>] block
    #         The block, which takes each of the matched files, transforms it if needed
    #         in a better comparable form in the temporary path, so that the temporary
    #         will be compared to a given after file, or makes appropriate expects, which
    #         depend on the used test framework
    #
    def transform_produced(*file_paths, &block)
      file_paths.each do |file_path|
        self.transform_paths[file_path] = block
      end
    end

    # Registers a block to preprocess certain files, matched with globs or
    # regular expressions.
    # Registered file paths will be excluded from default comparison by `diff`.
    # A file is preprocessed with the first matching preprocessor.
    #
    # @param  [String|Regexp...] file_paths
    #         The path(s)s, where an individual file diff handling is needed
    #
    # @param  [Block<(Pathname) -> (String)>] block
    #         The block, which takes each of the matched files, transforms it if needed
    #         in a better comparable form.
    #
    def preprocess(*file_paths, &block)
      file_paths.each do |file_path|
        self.preprocess_paths[file_path] = block
      end
    end

    # Copies the before subdirectory of the given tests folder in the temporary
    # directory.
    #
    # @param  [String|RegExp...] file_paths
    #         the file path(s) of the files to exclude from comparison
    #
    def ignores(*file_paths)
      self.ignore_paths += file_paths
    end


    #-----------------------------------------------------------------------------#

    # @!group Path accessors

    # Returns a list of transformers to apply for a given file path.
    #
    # @param  [Pathname] file_path
    #         The file path to match
    #
    # @return [Array<Block<(Pathname) -> ()>>]
    #
    def transformers_for(file_path)
      select_matching_file_patterns(transform_paths, file_path).values
    end

    # Returns a list of preprocessors to apply for a given file path.
    #
    # @param  [Pathname] file_path
    #         The file path to match
    #
    # @return [Array<Block<(Pathname) -> (String)>>]
    #
    def preprocessors_for(file_path)
      select_matching_file_patterns(preprocess_paths, file_path).values
    end

    # Checks whether a given file path is to ignore.
    #
    # @param  [Pathname] file_path
    #         The file path to match
    #
    # @return [Bool]
    #
    def ignores?(file_path)
      !select_matching_file_patterns(ignore_paths, file_path).empty?
    end


    #-----------------------------------------------------------------------------#

    # @!group Interaction

    # Prepare the temporary directory and the attribute #temp_path itself.
    #
    def prepare!
      temp_path.mkpath
      @temp_path = temp_path.realpath
    end

    # Get a specific spec with given folder to run it
    #
    # @param  [String] folder
    #         The name of the folder of the tests
    #
    # @param  [String] based_on
    #         @see FileTreeSpec#base_spec_name
    #
    # @return [FileTreeSpec]
    #
    def spec(spec_folder, based_on: nil)
      FileTreeSpec.new(self, spec_folder, based_on: based_on)
    end

    #-----------------------------------------------------------------------------#

    private

    # @!group Helpers

    # Select elements in an enumerable which match the given path.
    #
    # @param  [Enumerable<String|RegExp>] patterns
    #         The patterns to check
    #
    # @param  [Pathname] path
    #         The file to match
    #
    # @return [Enumerable<String|RegExp>]
    #
    def select_matching_file_patterns(patterns, path)
      patterns.select do |pattern|
        if pattern.is_a?(Regexp)
          path.to_s.match(pattern)
        else
          flags = File::FNM_PATHNAME
          flags |= File::FNM_DOTMATCH if include_hidden_files?
          File.fnmatch(pattern, path, flags)
        end
      end
    end

  end
end