Skip to content

Commit

Permalink
Add support for wildcards to filter chain
Browse files Browse the repository at this point in the history
This allows filter instatiation depending on options. Example from Slim:

    wildcard(:Optimizer) { (options[:streaming] ? Temple::Filters::StaticMerger :
                            Temple::Filters::DynamicInliner).new }
    wildcard(:Generator) { options[:generator].new(options) }
  • Loading branch information
minad committed Aug 24, 2011
1 parent b2cf848 commit 78f5a8a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 37 deletions.
5 changes: 3 additions & 2 deletions lib/temple/engine.rb
Expand Up @@ -63,11 +63,12 @@ def call_chain
filtered_options = Hash[*option_filter.select {|k| options.include?(k) }.map {|k| [k, options[k]] }.flatten]
filter.new(ImmutableHash.new(local_options, filtered_options))
when UnboundMethod
filter.bind(self)
filter = filter.bind(self)
filter.arity == 0 ? filter.call : filter
else
filter
end
end
end.compact
end
end
end
26 changes: 18 additions & 8 deletions lib/temple/mixins/engine_dsl.rb
Expand Up @@ -86,8 +86,24 @@ def generator(name, *options, &block)
use(name, Temple::Generators.const_get(name), *options, &block)
end

def wildcard(name, &block)
raise(ArgumentError, 'Block must have arity 0') unless block.arity == 0
chain << [name, define_chain_method("WILDCARD #{name}", block)]
chain_modified!
end

private

def define_chain_method(name, proc)
if Class === self
define_method(name, &proc)
instance_method(name)
else
(class << self; self; end).class_eval { define_method(name, &proc) }
method(name)
end
end

def element(args, block)
name = args.shift
if Class === name
Expand All @@ -110,14 +126,7 @@ def element(args, block)
# The proc can then access the option hash of the engine.
raise(ArgumentError, 'Too many arguments') unless args.empty?
raise(ArgumentError, 'Proc or blocks must have arity 1') unless filter.arity == 1
method_name = "FILTER #{name}"
if Class === self
define_method(method_name, &filter)
[name, instance_method(method_name)]
else
(class << self; self; end).class_eval { define_method(method_name, &filter) }
[name, method(method_name)]
end
[name, define_chain_method("FILTER #{name}", filter)]
when Class
# Class argument (e.g Filter class)
# The options are passed to the classes constructor.
Expand All @@ -127,6 +136,7 @@ def element(args, block)
else
# Other callable argument (e.g. Object of class which implements #call or Method)
# The callable has no access to the option hash of the engine.
raise(ArgumentError, 'Too many arguments') unless args.empty?
raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call)
[name, filter]
end
Expand Down
66 changes: 39 additions & 27 deletions test/test_engine.rb
@@ -1,7 +1,13 @@
# -*- coding: utf-8 -*-
require 'helper'

class OtherCallable
class Callable1
def call(exp)
exp
end
end

class Callable2
def call(exp)
exp
end
Expand All @@ -16,12 +22,13 @@ class TestEngine < Temple::Engine
use Temple::HTML::Pretty, :format, :pretty => true
filter :MultiFlattener
generator :ArrayBuffer
use :FinalFilter, OtherCallable.new
use :BeforeLast, Callable1.new
wildcard(:Last) { Callable2.new }
end

describe Temple::Engine do
it 'should build chain' do
TestEngine.chain.size.should.equal 7
TestEngine.chain.size.should.equal 8

TestEngine.chain[0].first.should.equal :Parser
TestEngine.chain[0].size.should.equal 2
Expand All @@ -45,8 +52,12 @@ class TestEngine < Temple::Engine
TestEngine.chain[5].should.equal [:ArrayBuffer, Temple::Generators::ArrayBuffer, [], nil]

TestEngine.chain[6].size.should.equal 2
TestEngine.chain[6][0].should.equal :FinalFilter
TestEngine.chain[6][1].should.be.instance_of OtherCallable
TestEngine.chain[6][0].should.equal :BeforeLast
TestEngine.chain[6][1].should.be.instance_of Callable1

TestEngine.chain[7].size.should.equal 2
TestEngine.chain[7][0].should.equal :Last
TestEngine.chain[7][1].should.be.instance_of UnboundMethod
end

it 'should instantiate chain' do
Expand All @@ -57,47 +68,48 @@ class TestEngine < Temple::Engine
call_chain[3].should.be.instance_of Temple::HTML::Pretty
call_chain[4].should.be.instance_of Temple::Filters::MultiFlattener
call_chain[5].should.be.instance_of Temple::Generators::ArrayBuffer
call_chain[6].should.be.instance_of OtherCallable
call_chain[6].should.be.instance_of Callable1
call_chain[7].should.be.instance_of Callable2
end

it 'should have #append' do
engine = TestEngine.new
call_chain = engine.send(:call_chain)
call_chain.size.should.equal 7
call_chain.size.should.equal 8

engine.append :MyFilter3 do |exp|
exp
end

TestEngine.chain.size.should.equal 7
engine.chain.size.should.equal 8
engine.chain[7].first.should.equal :MyFilter3
engine.chain[7].size.should.equal 2
engine.chain[7].last.should.be.instance_of Method
TestEngine.chain.size.should.equal 8
engine.chain.size.should.equal 9
engine.chain[8].first.should.equal :MyFilter3
engine.chain[8].size.should.equal 2
engine.chain[8].last.should.be.instance_of Method

call_chain = engine.send(:call_chain)
call_chain.size.should.equal 8
call_chain[7].should.be.instance_of Method
call_chain.size.should.equal 9
call_chain[8].should.be.instance_of Method
end

it 'should have #prepend' do
engine = TestEngine.new
call_chain = engine.send(:call_chain)
call_chain.size.should.equal 7
call_chain.size.should.equal 8

engine.prepend :MyFilter0 do |exp|
exp
end

TestEngine.chain.size.should.equal 7
engine.chain.size.should.equal 8
TestEngine.chain.size.should.equal 8
engine.chain.size.should.equal 9
engine.chain[0].first.should.equal :MyFilter0
engine.chain[0].size.should.equal 2
engine.chain[0].last.should.be.instance_of Method
engine.chain[1].first.should.equal :Parser

call_chain = engine.send(:call_chain)
call_chain.size.should.equal 8
call_chain.size.should.equal 9
call_chain[0].should.be.instance_of Method
end

Expand All @@ -106,7 +118,7 @@ class TestEngine < Temple::Engine
engine.after :Parser, :MyFilter0 do |exp|
exp
end
engine.chain.size.should.equal 8
engine.chain.size.should.equal 9
engine.chain[0].first.should.equal :Parser
engine.chain[1].first.should.equal :MyFilter0
engine.chain[2].first.should.equal :MyFilter1
Expand All @@ -117,7 +129,7 @@ class TestEngine < Temple::Engine
engine.before :MyFilter1, :MyFilter0 do |exp|
exp
end
engine.chain.size.should.equal 8
engine.chain.size.should.equal 9
engine.chain[0].first.should.equal :Parser
engine.chain[1].first.should.equal :MyFilter0
engine.chain[2].first.should.equal :MyFilter1
Expand All @@ -126,7 +138,7 @@ class TestEngine < Temple::Engine
it 'should have #remove' do
engine = TestEngine.new
engine.remove :MyFilter1
engine.chain.size.should.equal 6
engine.chain.size.should.equal 7
engine.chain[0].first.should.equal :Parser
engine.chain[1].first.should.equal :MyFilter2
end
Expand All @@ -136,24 +148,24 @@ class TestEngine < Temple::Engine
engine.before :Parser, :MyParser do |exp|
exp
end
engine.chain.size.should.equal 8
engine.chain.size.should.equal 9
engine.chain[0].first.should.equal :MyParser
end

it 'should work with inheritance' do
inherited_engine = Class.new(TestEngine)
inherited_engine.chain.size.should.equal 7
inherited_engine.chain.size.should.equal 8
inherited_engine.append :MyFilter3 do |exp|
exp
end
inherited_engine.chain.size.should.equal 8
TestEngine.chain.size.should.equal 7
inherited_engine.chain.size.should.equal 9
TestEngine.chain.size.should.equal 8
end

it 'should support chain option' do
engine = TestEngine.new(:chain => proc {|e| e.remove :MyFilter1 })
TestEngine.chain.size.should.equal 7
engine.chain.size.should.equal 6
TestEngine.chain.size.should.equal 8
engine.chain.size.should.equal 7
engine.chain[0].first.should.equal :Parser
engine.chain[1].first.should.equal :MyFilter2
end
Expand Down

0 comments on commit 78f5a8a

Please sign in to comment.