Skip to content

Implementing the TiltYieldingFilter #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/rake-pipeline-web-filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ module Filters
require "rake-pipeline-web-filters/chained_filter"
require "rake-pipeline-web-filters/less_filter"
require "rake-pipeline-web-filters/handlebars_filter"
require "rake-pipeline-web-filters/tilt_yielding_filter"
require "rake-pipeline-web-filters/helpers"
6 changes: 6 additions & 0 deletions lib/rake-pipeline-web-filters/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def less(*args, &block)
def handlebars(*args, &block)
filter(Rake::Pipeline::Web::Filters::HandlebarsFilter, *args, &block)
end

# Add a new {TiltYieldingFilter} to the pipeline.
# @see TiltYieldingFilter#initialize
def tilt_yielding(*args, &block)
filter(Rake::Pipeline::Web::Filters::TiltYieldingFilter, *args, &block)
end
end
end

Expand Down
98 changes: 98 additions & 0 deletions lib/rake-pipeline-web-filters/tilt_yielding_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
module Rake::Pipeline::Web::Filters
# A filter that generates Tilt templates where the templates may yield
#
# @example
# !!!ruby
# Rake::Pipeline.build do
# output "public"
#
# # Below, the layout yields to the index; i.e., there will be a
# # call to yield inside layout.slim. The resulting file generated
# # will be index.html
# input "assets" do
# match "templates/{layout,index}.slim" do
# tilt_yielding :yields_to => ["layout","index"], :output_name => "index.html"
# end
# end
#
# # If you need to match the same layout again, it needs to be in
# # its own input block
# input "assets" do
# match "templates/{layout,about}.slim" do
# tilt_yielding :yields_to => ["layout","about"], :output_name => "about.html"
# end
# end
# end
class TiltYieldingFilter < Rake::Pipeline::Filter

include Rake::Pipeline::Web::Filters::FilterWithDependencies

# @param [Hash] options is used by the filter and the generator
# @option options [String] :output_name is the file name that will be
# used for the resulting file from generation
# @option options [Array] :yields_to is the array in which the
# templates will be ordered upon generation, for instance, given
# [a,b,c], a yields_to b and b yields_to c, which means that the
# result of c is nested in b, and that result is nested in a.
# @option options [Object] :scope is the scope that will be passed
# on each render of the templates
# @option options [Hash] :locals is the Hash of local variables that
# will be passed on each render of the templates
# @param [Proc] block a block to use as the Filter's
# {#output_name_generator}, which by default will group all the
# inputs to the @output_name. Note that if this is proc is
# overridden then it is important that inputs are grouped to the
# result of the proc
def initialize options = {}, &block
@output_name = options.delete(:output_name) || DEFAULT_OUTPUT_NAME
@yields_to = options.delete(:yields_to) || []
@scope = options.delete(:scope) || Object.new
@locals = options.delete(:locals) || {}
@options = options
super(&block || ->_ {@output_name})
end

def generate_output inputs, output
output.write invoke_tilt(order_inputs inputs)
end

private
DEFAULT_OUTPUT_NAME = "a.out"
def external_dependencies; ["tilt"]; end

# Determines if the value matches the given input, where the value
# will be an item in the @yields_to array, and the input will be an
# item in the array of inputs. The method returns true when the file
# name of the input without an extension equals the value.
def matches_input? value, input
File.basename(input.path,File.extname(input.path)) == value
end

# Orders the inputs according to the @yields_to array. This method
# takes a list of inputs and returns the same list of inputs,
# potentially in a different order. The reverse occurs at the end
# because Tile must process the inner-most template first and pass
# it up the yields_to chain. So the user specifies the @yields_to in
# terms of a yields to b, but we process b and pass it in when a is
# processed.
def order_inputs inputs
case @yields_to
when [] then inputs
else
@yields_to.
map {|a| inputs.index {|b| matches_input? a,b}}.
map {|a| inputs[a]}
end.reverse
end

# Generates the result of invoking tilt on each of the inputs where
# each sequential input is nesteded within the next input. So we
# expect that the ordering of the inputs is bottom to top.
def invoke_tilt inputs
inputs.reduce("") do |b,a|
# Filename, line number, options
Tilt[a.path].new(nil,1,@options) {|_| a.read}.render(@scope,@locals) {b}
end
end
end
end
8 changes: 7 additions & 1 deletion spec/helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@ def filter
end
end


describe "#handlebars" do
it "creates a HandlebarsFilter" do
dsl.handlebars
filter.should be_kind_of(Rake::Pipeline::Web::Filters::HandlebarsFilter)
end
end

describe "#tilt_yielding" do
it "creates a TiltYieldingFilter" do
dsl.tilt_yielding
filter.should be_kind_of(Rake::Pipeline::Web::Filters::TiltYieldingFilter)
end
end
end
134 changes: 134 additions & 0 deletions spec/tilt_yielding_filter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
describe "TiltYieldingFilter" do
TiltYieldingFilter ||= Rake::Pipeline::Web::Filters::TiltYieldingFilter
MemoryFileWrapper ||= Rake::Pipeline::SpecHelpers::MemoryFileWrapper

before do
@input_path = "/path/to/input"
@output_path = "/path/to/output"
@encoding = "UTF-8"
@default_output = "a.out"
end

before do
@input_files = [
["first.erb","First <%= yield %> /First"],
["second.erb","Second <%= yield %> /Second"],
["third.erb","Third"],
].map{|a| MemoryFileWrapper.new @input_path, a.first, @encoding, a.last}
end

describe "when output is generated using default options" do
before do
@filter = TiltYieldingFilter.new
@filter.file_wrapper_class = MemoryFileWrapper
@filter.input_files = @input_files
@filter.output_root = @output_path
@filter.rake_application = Rake::Application.new
end

before do
@expected_output = "First Second Third /Second /First"
end

it "should group the input files into one rake task" do
tasks = @filter.generate_rake_tasks
tasks.length.should == 1
tasks.first.prerequisites.should =~ @input_files.map{|a| "#{a.root}/#{a.path}"}
end

it "should generate the output nesting each input file at the yield" do
tasks = @filter.generate_rake_tasks
tasks.each(&:invoke)
MemoryFileWrapper.files["#{@output_path}/#{@default_output}"].body.should == @expected_output
end
end

describe "when a yield order is provided for the generated output" do
before do
@yields_to = ["second","first","third"]
end

before do
@filter = TiltYieldingFilter.new :yields_to => @yields_to
@filter.file_wrapper_class = MemoryFileWrapper
@filter.input_files = @input_files
@filter.output_root = @output_path
@filter.rake_application = Rake::Application.new
end

before do
@expected_output = "Second First Third /First /Second"
end

it "should generate the output nesting each input file at the yield with respect to the ordering" do
tasks = @filter.generate_rake_tasks
tasks.each(&:invoke)
MemoryFileWrapper.files["#{@output_path}/#{@default_output}"].body.should == @expected_output
end
end

describe "when output is generated using a locals or a scope" do
before do
@input_files = [
["first.erb","<%= first %> <%= yield %> /<%= first %>"],
["second.erb","<%= second %> <%= yield %> /<%= second %>"],
["third.erb","<%= third %>"],
].map{|a| MemoryFileWrapper.new @input_path, a.first, @encoding, a.last}
end

before do
@first = "abc"
@second = "xyz"
@third = "oh snap!"
@expected_output = "#{@first} #{@second} #{@third} /#{@second} /#{@first}"
end

describe "when locals are used to define the variables" do
before do
@filter = TiltYieldingFilter.new :locals => {
:first => @first,
:second => @second,
:third => @third
}
@filter.file_wrapper_class = MemoryFileWrapper
@filter.input_files = @input_files
@filter.output_root = @output_path
@filter.rake_application = Rake::Application.new
end

it "should generate the output nesting each input file at the yield" do
tasks = @filter.generate_rake_tasks
tasks.each(&:invoke)
MemoryFileWrapper.files["#{@output_path}/#{@default_output}"].body.should == @expected_output
end
end

describe "when a scope is used to define the variables" do
before do
@scope = Class.new do
def initialize first,second,third
@first = first
@second = second
@third = third
end
def first; @first; end
def second; @second; end
def third; @third; end
end.new(@first,@second,@third)
end
before do
@filter = TiltYieldingFilter.new :scope => @scope
@filter.file_wrapper_class = MemoryFileWrapper
@filter.input_files = @input_files
@filter.output_root = @output_path
@filter.rake_application = Rake::Application.new
end

it "should generate the output nesting each input file at the yield" do
tasks = @filter.generate_rake_tasks
tasks.each(&:invoke)
MemoryFileWrapper.files["#{@output_path}/#{@default_output}"].body.should == @expected_output
end
end
end
end