livingsocial / rake-pipeline

An extension to Rake for dealing with a directory of inputs, a number of filters, and a directory of outputs
MIT License
276 stars 38 forks source link

Feature: Directory Matcher #87

Open ahawkins opened 12 years ago

ahawkins commented 12 years ago

Here's a use case:

/sprites contains many directories. Each directory represents one sprite.

How can we do this? Here's one possible solution:

Dir['/sprites/*'].each do "sprite_name"
  match "/sprites/#{sprite_name}/*.png" do
    sprite
  end
end

Problems with this use case: all sprites have to be known as pipeline load time. Load time is not the same as build time (using the preview server).

Here's another solution: use a directory matcher to iterate over directories and provide their contents as inputs to a filter.

directory "images/sprites" do
  sprite
end

I think this is a valid use case. The use case itself is abstract and useful in other scenarios.

I've included my rough implementation of this in the issue to see if there is interest in this functionality.

I'd change this so that the DirectoryFilter can also be used in other pipelines. If the parent pipeline is a DirectoryMatcher then do the custom behavior. Else call super.

module Rake::Pipeline::Web::Filters
  class DirectoryMatcher < Rake::Pipeline::Matcher

    # Take files matching the directory glob from the set of all files.
    # Return an array of array where each element in the array is
    # the contents of an input directory
    def eligible_input_files
      directory_files = input_files.select do |file|
        file.path.include? @glob
      end

      directory_files.inject({}) do |memo, input|
        directory_name = File.dirname(input.path)
        memo[directory_name] ||= []
        memo[directory_name] << input
        memo
      end.values
    end
  end

  class DirectoryFilter < Rake::Pipeline::Filter
    def initialize(*args, &block)
      block ||= proc { |input| File.basename input }
      super &block
    end

    # We are working with an array of arrays to act accordingly
    def input_files=(files)
      @input_files = files.map do |directory|
        directory.map { |f| f.with_encoding(encoding) }
      end
    end

    # Outputs look like this:
    # 
    #  {
    #    generated_output_name => directory_contents,
    #    generated_output_name => directory_contents.
    #  }
    #
    # This ensures the filter's `generate_output` method is called
    # once for each directory
    def outputs
      hash = {}

      input_files.each do |directory|
        directory_name = File.dirname directory.first.path

        path = output_name_generator.call(*directory_name)

        output_file = file_wrapper_class.new(output_root, path, encoding)

        hash[output_file] = directory
      end

      hash
    end

    def output_files
      outputs.keys
    end

    # Just like normal
    def generate_output(inputs, output)
    end
  end

  module PipelineHelpers
    def directory(pattern, &block)
      matcher = pipeline.copy(DirectoryMatcher, &block)
      matcher.glob = pattern
      pipeline.add_filter matcher
      matcher
    end

    def directory_filter(*args, &block)
      filter Rake::Pipeline::Web::Filters::DirectoryFilter, *args, &block
    end
  end
end
mulderp commented 12 years ago

Would there be a N:1 mapping of files/directories? I am looking for something like this in case of compilimg Less files in sub-directories for CSS