temple-0.6.6/0000755000004100000410000000000012200421022013022 5ustar www-datawww-datatemple-0.6.6/temple.gemspec0000644000004100000410000000166312200421022015663 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.dirname(__FILE__) + '/lib/temple/version' require 'date' Gem::Specification.new do |s| s.name = 'temple' s.version = Temple::VERSION s.date = Date.today.to_s s.authors = ['Magnus Holm', 'Daniel Mendler'] s.email = ['judofyr@gmail.com', 'mail@daniel-mendler.de'] s.homepage = 'https://github.com/judofyr/temple' s.summary = 'Template compilation framework in Ruby' s.require_paths = %w(lib) s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.license = 'MIT' # Tilt is only development dependency because most parts of Temple # can be used without it. s.add_development_dependency('tilt') s.add_development_dependency('bacon') s.add_development_dependency('rake') end temple-0.6.6/.travis.yml0000644000004100000410000000024512200421022015134 0ustar www-datawww-datalanguage: ruby rvm: - 1.8.7 - 1.9.3 - 2.0.0 - ruby-head - jruby-18mode - jruby-19mode - rbx-18mode - rbx-19mode before_install: - ./kill-travis.sh temple-0.6.6/test/0000755000004100000410000000000012200421022014001 5ustar www-datawww-datatemple-0.6.6/test/mixins/0000755000004100000410000000000012200421022015310 5ustar www-datawww-datatemple-0.6.6/test/mixins/test_grammar_dsl.rb0000644000004100000410000000443012200421022021165 0ustar www-datawww-datarequire 'helper' module BasicGrammar extend Temple::Mixins::GrammarDSL Expression << Symbol | Answer | [:zero_or_more, 'Expression*'] | [:one_or_more, 'Expression+'] | [:zero_or_one, 'Expression?'] | [:bool, Bool] | nil Bool << true | false Answer << Value(42) end module ExtendedGrammar extend BasicGrammar Expression << [:extended, Expression] end describe Temple::Mixins::GrammarDSL do it 'should support class types' do BasicGrammar.should.match :symbol BasicGrammar.should.not.match [:symbol] BasicGrammar.should.not.match 'string' BasicGrammar.should.not.match ['string'] end it 'should support value types' do BasicGrammar.should.match 42 BasicGrammar.should.not.match 43 end it 'should support nesting' do BasicGrammar.should.match [:zero_or_more, [:zero_or_more]] end it 'should support *' do BasicGrammar.should.match [:zero_or_more] BasicGrammar.should.match [:zero_or_more, nil, 42] end it 'should support +' do BasicGrammar.should.not.match [:one_or_more] BasicGrammar.should.match [:one_or_more, 42] BasicGrammar.should.match [:one_or_more, 42, nil] end it 'should support ?' do BasicGrammar.should.not.match [:zero_or_one, nil, 42] BasicGrammar.should.match [:zero_or_one] BasicGrammar.should.match [:zero_or_one, 42] end it 'should support extended grammars' do ExtendedGrammar.should.match [:extended, [:extended, 42]] BasicGrammar.should.not.match [:zero_or_more, [:extended, nil]] BasicGrammar.should.not.match [:extended, [:extended, 42]] end it 'should have validate!' do grammar_validate BasicGrammar, [:zero_or_more, [:zero_or_more, [:unknown]]], "BasicGrammar::Expression did not match\n[:unknown]\n" grammar_validate BasicGrammar, [:zero_or_more, [:one_or_more]], "BasicGrammar::Expression did not match\n[:one_or_more]\n" grammar_validate BasicGrammar, [:zero_or_more, 123, [:unknown]], "BasicGrammar::Expression did not match\n123\n" grammar_validate BasicGrammar, [:bool, 123], "BasicGrammar::Bool did not match\n123\n" end end temple-0.6.6/test/mixins/test_dispatcher.rb0000644000004100000410000000314312200421022021023 0ustar www-datawww-datarequire 'helper' class FilterWithDispatcherMixin include Temple::Mixins::Dispatcher def on_test(arg) [:on_test, arg] end def on_test_check(arg) [:on_check, arg] end def on_second_test(arg) [:on_second_test, arg] end def on_a_b(*arg) [:on_ab, *arg] end def on_a_b_test(arg) [:on_ab_test, arg] end def on_a_b_c_d_test(arg) [:on_abcd_test, arg] end end class FilterWithDispatcherMixinAndOn < FilterWithDispatcherMixin def on(*args) [:on_zero, *args] end end describe Temple::Mixins::Dispatcher do before do @filter = FilterWithDispatcherMixin.new end it 'should return unhandled expressions' do @filter.call([:unhandled]).should.equal [:unhandled] end it 'should dispatch first level' do @filter.call([:test, 42]).should.equal [:on_test, 42] end it 'should dispatch second level' do @filter.call([:second, :test, 42]).should.equal [:on_second_test, 42] end it 'should dispatch second level if prefixed' do @filter.call([:test, :check, 42]).should.equal [:on_check, 42] end it 'should dispatch parent level' do @filter.call([:a, 42]).should == [:a, 42] @filter.call([:a, :b, 42]).should == [:on_ab, 42] @filter.call([:a, :b, :test, 42]).should == [:on_ab_test, 42] @filter.call([:a, :b, :c, 42]).should == [:on_ab, :c, 42] @filter.call([:a, :b, :c, :d, 42]).should == [:on_ab, :c, :d, 42] @filter.call([:a, :b, :c, :d, :test, 42]).should == [:on_abcd_test, 42] end it 'should dispatch zero level' do FilterWithDispatcherMixinAndOn.new.call([:foo,42]).should == [:on_zero, :foo, 42] end end temple-0.6.6/test/test_erb.rb0000644000004100000410000000206712200421022016142 0ustar www-datawww-datarequire 'helper' require 'erb' require 'tilt' describe Temple::ERB::Engine do it 'should compile erb' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } erb(src).should.equal ERB.new(src).result end it 'should recognize comments' do src = %q{ hello <%# comment -- ignored -- useful in testing %> world} erb(src).should.equal ERB.new(src).result end it 'should recognize <%% and %%>' do src = %q{ <%% <% if true %> %%> <% end %> } erb(src).should.equal "\n<%\n\n %>\n\n" #ERB.new(src).result end it 'should escape automatically' do src = '<%= "<" %>' ans = '<' erb(src).should.equal ans end it 'should support == to disable automatic escape' do src = '<%== "<" %>' ans = '<' erb(src).should.equal ans end it 'should support trim mode' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } erb(src, :trim_mode => '>').should.equal ERB.new(src, nil, '>').result erb(src, :trim_mode => '<>').should.equal ERB.new(src, nil, '<>').result end end temple-0.6.6/test/test_filter.rb0000644000004100000410000000141412200421022016652 0ustar www-datawww-datarequire 'helper' class SimpleFilter < Temple::Filter define_options :key def on_test(arg) [:on_test, arg] end end describe Temple::Filter do it 'should support options' do Temple::Filter.should.respond_to :default_options Temple::Filter.should.respond_to :set_default_options Temple::Filter.should.respond_to :define_options Temple::Filter.new.options.should.be.instance_of Temple::ImmutableHash SimpleFilter.new(:key => 3).options[:key].should.equal 3 end it 'should implement call' do Temple::Filter.new.call([:exp]).should.equal [:exp] end it 'should process expressions' do filter = SimpleFilter.new filter.call([:unhandled]).should.equal [:unhandled] filter.call([:test, 42]).should.equal [:on_test, 42] end end temple-0.6.6/test/test_utils.rb0000644000004100000410000000220412200421022016523 0ustar www-datawww-datarequire 'helper' class UniqueTest include Temple::Utils end describe Temple::Utils do it 'has empty_exp?' do Temple::Utils.empty_exp?([:multi]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi]]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi, [:newline]], [:newline]]).should.be.true Temple::Utils.empty_exp?([:multi]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi, [:static, 'text']]]).should.be.false Temple::Utils.empty_exp?([:multi, [:newline], [:multi, [:dynamic, 'text']]]).should.be.false end it 'has unique_name' do u = UniqueTest.new u.unique_name.should.equal '_uniquetest1' u.unique_name.should.equal '_uniquetest2' UniqueTest.new.unique_name.should.equal '_uniquetest1' end it 'has escape_html' do Temple::Utils.escape_html('<').should.equal '<' end it 'should escape unsafe html strings' do with_html_safe do Temple::Utils.escape_html_safe('<').should.equal '<' end end it 'should not escape safe html strings' do with_html_safe do Temple::Utils.escape_html_safe('<'.html_safe).should.equal '<' end end end temple-0.6.6/test/filters/0000755000004100000410000000000012200421022015451 5ustar www-datawww-datatemple-0.6.6/test/filters/test_code_merger.rb0000644000004100000410000000142412200421022021311 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::CodeMerger do before do @filter = Temple::Filters::CodeMerger.new end it 'should merge serveral codes' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:code, "c"] ]).should.equal [:code, "a; b; c"] end it 'should merge serveral codes around static' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:static, "123"], [:code, "a"], [:code, "b"] ]).should.equal [:multi, [:code, "a; b"], [:static, "123"], [:code, "a; b"] ] end it 'should merge serveral codes with newlines' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:newline], [:code, "c"] ]).should.equal [:code, "a; b\nc"] end end temple-0.6.6/test/filters/test_escapable.rb0000644000004100000410000000243112200421022020754 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::Escapable do before do @filter = Temple::Filters::Escapable.new end it 'should handle escape expressions' do @filter.call([:escape, true, [:multi, [:static, "a < b"], [:dynamic, "ruby_method"]] ]).should.equal [:multi, [:static, "a < b"], [:dynamic, "::Temple::Utils.escape_html((ruby_method))"], ] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] @filter.call(exp).should.equal exp end it 'should keep statics intact' do exp = [:multi, [:static, '<']] @filter.call(exp).should.equal exp end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] @filter.call(exp).should.equal exp end it 'should have use_html_safe option' do filter = Temple::Filters::Escapable.new(:use_html_safe => true) filter.call([:escape, true, [:static, HtmlSafeString.new("a < b")] ]).should.equal [:static, "a < b"] end it 'should support censoring' do filter = Temple::Filters::Escapable.new(:escape_code => '(%s).gsub("Temple sucks", "Temple rocks")') filter.call([:escape, true, [:static, "~~ Temple sucks ~~"] ]).should.equal [:static, "~~ Temple rocks ~~"] end end temple-0.6.6/test/filters/test_static_merger.rb0000644000004100000410000000173312200421022021671 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::StaticMerger do before do @filter = Temple::Filters::StaticMerger.new end it 'should merge serveral statics' do @filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:static, "Good night"] ]).should.equal [:static, "Hello World, Good night"] end it 'should merge serveral statics around code' do @filter.call([:multi, [:static, "Hello "], [:static, "World!"], [:code, "123"], [:static, "Good night, "], [:static, "everybody"] ]).should.equal [:multi, [:static, "Hello World!"], [:code, "123"], [:static, "Good night, everybody"] ] end it 'should merge serveral statics across newlines' do @filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:newline], [:static, "Good night"] ]).should.equal [:multi, [:static, "Hello World, Good night"], [:newline] ] end end temple-0.6.6/test/filters/test_control_flow.rb0000644000004100000410000000365512200421022021555 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::ControlFlow do before do @filter = Temple::Filters::ControlFlow.new end it 'should process blocks' do @filter.call([:block, 'loop do', [:static, 'Hello'] ]).should.equal [:multi, [:code, 'loop do'], [:static, 'Hello'], [:code, 'end']] end it 'should process if' do @filter.call([:if, 'condition', [:static, 'Hello'] ]).should.equal [:multi, [:code, 'if condition'], [:static, 'Hello'], [:code, 'end'] ] end it 'should process if with else' do @filter.call([:if, 'condition', [:static, 'True'], [:static, 'False'] ]).should.equal [:multi, [:code, 'if condition'], [:static, 'True'], [:code, 'else'], [:static, 'False'], [:code, 'end'] ] end it 'should create elsif' do @filter.call([:if, 'condition1', [:static, '1'], [:if, 'condition2', [:static, '2'], [:static, '3']] ]).should.equal [:multi, [:code, 'if condition1'], [:static, '1'], [:code, 'elsif condition2'], [:static, '2'], [:code, 'else'], [:static, '3'], [:code, 'end'] ] end it 'should process cond' do @filter.call([:cond, ['cond1', [:exp1]], ['cond2', [:exp2]], [:else, [:exp3]], ]).should.equal [:multi, [:code, 'case'], [:code, 'when cond1'], [:exp1], [:code, 'when cond2'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end it 'should process case' do @filter.call([:case, 'var', ['Array', [:exp1]], ['String', [:exp2]], [:else, [:exp3]], ]).should.equal [:multi, [:code, 'case (var)'], [:code, 'when Array'], [:exp1], [:code, 'when String'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end end temple-0.6.6/test/filters/test_multi_flattener.rb0000644000004100000410000000130012200421022022225 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::MultiFlattener do before do @filter = Temple::Filters::MultiFlattener.new end it 'should flatten nested multi expressions' do @filter.call([:multi, [:static, "a"], [:multi, [:dynamic, "aa"], [:multi, [:static, "aaa"], [:static, "aab"], ], [:dynamic, "ab"], ], [:static, "b"], ]).should.equal [:multi, [:static, "a"], [:dynamic, "aa"], [:static, "aaa"], [:static, "aab"], [:dynamic, "ab"], [:static, "b"], ] end it 'should return first element' do @filter.call([:multi, [:code, 'foo']]).should.equal [:code, 'foo'] end end temple-0.6.6/test/filters/test_dynamic_inliner.rb0000644000004100000410000000473612200421022022213 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::DynamicInliner do before do @filter = Temple::Filters::DynamicInliner.new end it 'should compile several statics into dynamic' do @filter.call([:multi, [:static, "Hello "], [:static, "World\n "], [:static, "Have a nice day"] ]).should.equal [:dynamic, '"Hello World\n Have a nice day"'] end it 'should compile several dynamics into dynamic' do @filter.call([:multi, [:dynamic, "@hello"], [:dynamic, "@world"], [:dynamic, "@yeah"] ]).should.equal [:dynamic, '"#{@hello}#{@world}#{@yeah}"'] end it 'should compile static and dynamic into dynamic' do @filter.call([:multi, [:static, "Hello"], [:dynamic, "@world"], [:dynamic, "@yeah"], [:static, "Nice"] ]).should.equal [:dynamic, '"Hello#{@world}#{@yeah}Nice"'] end it 'should merge statics and dynamics around a code' do exp = @filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:code, "Oh yeah"], [:dynamic, "@yeah"], [:static, "Once more"] ]).should.equal [:multi, [:dynamic, '"Hello #{@world}"'], [:code, "Oh yeah"], [:dynamic, '"#{@yeah}Once more"'] ] end it 'should keep codes intact' do @filter.call([:multi, [:code, 'foo']]).should.equal [:code, 'foo'] end it 'should keep single statics intact' do @filter.call([:multi, [:static, 'foo']]).should.equal [:static, 'foo'] end it 'should keep single dynamic intact' do @filter.call([:multi, [:dynamic, 'foo']]).should.equal [:dynamic, 'foo'] end it 'should inline inside multi' do @filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:multi, [:static, "Hello "], [:dynamic, "@world"]], [:static, "Hello "], [:dynamic, "@world"] ]).should.equal [:multi, [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'] ] end it 'should merge across newlines' do exp = @filter.call([:multi, [:static, "Hello \n"], [:newline], [:dynamic, "@world"], [:newline] ]).should.equal [:dynamic, ['"Hello \n"', '"#{@world}"', '""'].join("\\\n")] end it 'should compile static followed by newline' do @filter.call([:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ]).should.equal [:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ] end end temple-0.6.6/test/filters/test_eraser.rb0000644000004100000410000000200612200421022020314 0ustar www-datawww-datarequire 'helper' describe Temple::Filters::Eraser do it 'should respect keep' do eraser = Temple::Filters::Eraser.new(:keep => [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:a], [:multi], [:multi] ] end it 'should respect erase' do eraser = Temple::Filters::Eraser.new(:erase => [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:multi], [:b], [:c] ] end it 'should choose erase over keep' do eraser = Temple::Filters::Eraser.new(:keep => [:a, :b], :erase => [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:multi], [:b], [:multi] ] end it 'should erase nested types' do eraser = Temple::Filters::Eraser.new(:erase => [[:a, :b]]) eraser.call([:multi, [:a, :a], [:a, :b], [:b] ]).should.equal [:multi, [:a, :a], [:multi], [:b] ] end end temple-0.6.6/test/helper.rb0000644000004100000410000000142112200421022015603 0ustar www-datawww-datarequire 'bacon' require 'temple' class HtmlSafeString < String def html_safe? true end def to_s self end end module TestHelper def with_html_safe String.send(:define_method, :html_safe?) { false } String.send(:define_method, :html_safe) { HtmlSafeString.new(self) } yield ensure String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?) String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe) end def grammar_validate(grammar, exp, message) lambda { grammar.validate!(exp) }.should.raise(Temple::InvalidExpression).message.should.equal message end def erb(src, options = {}) Temple::ERB::Template.new(options) { src }.render end end class Bacon::Context include TestHelper end temple-0.6.6/test/test_generator.rb0000644000004100000410000001140512200421022017354 0ustar www-datawww-datarequire 'helper' class SimpleGenerator < Temple::Generator def preamble "#{buffer} = BUFFER" end def postamble buffer end def on_static(s) concat "S:#{s}" end def on_dynamic(s) concat "D:#{s}" end def on_code(s) "C:#{s}" end end describe Temple::Generator do it 'should compile simple expressions' do gen = SimpleGenerator.new gen.call([:static, 'test']).should.equal '_buf = BUFFER; _buf << (S:test); _buf' gen.call([:dynamic, 'test']).should.equal '_buf = BUFFER; _buf << (D:test); _buf' gen.call([:code, 'test']).should.equal '_buf = BUFFER; C:test; _buf' end it 'should compile multi expression' do gen = SimpleGenerator.new(:buffer => "VAR") gen.call([:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"] ]).should.equal 'VAR = BUFFER; VAR << (S:static); VAR << (D:dynamic); C:code; VAR' end it 'should compile capture' do gen = SimpleGenerator.new(:buffer => "VAR", :capture_generator => SimpleGenerator) gen.call([:capture, "foo", [:static, "test"] ]).should.equal 'VAR = BUFFER; foo = BUFFER; foo << (S:test); foo; VAR' end it 'should compile capture with multi' do gen = SimpleGenerator.new(:buffer => "VAR", :capture_generator => SimpleGenerator) gen.call([:multi, [:static, "before"], [:capture, "foo", [:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"]]], [:static, "after"] ]).should.equal 'VAR = BUFFER; VAR << (S:before); foo = BUFFER; foo << (S:static); ' + 'foo << (D:dynamic); C:code; foo; VAR << (S:after); VAR' end it 'should compile newlines' do gen = SimpleGenerator.new(:buffer => "VAR") gen.call([:multi, [:static, "static"], [:newline], [:dynamic, "dynamic"], [:newline], [:code, "code"] ]).should.equal "VAR = BUFFER; VAR << (S:static); \n; " + "VAR << (D:dynamic); \n; C:code; VAR" end end describe Temple::Generators::Array do it 'should compile simple expressions' do gen = Temple::Generators::Array.new gen.call([:static, 'test']).should.equal '_buf = []; _buf << ("test"); _buf' gen.call([:dynamic, 'test']).should.equal '_buf = []; _buf << (test); _buf' gen.call([:code, 'test']).should.equal '_buf = []; test; _buf' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf' end end describe Temple::Generators::ArrayBuffer do it 'should compile simple expressions' do gen = Temple::Generators::ArrayBuffer.new gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:dynamic, 'test']).should.equal '_buf = (test).to_s' gen.call([:code, 'test']).should.equal '_buf = []; test; _buf = _buf.join' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf = _buf.join' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf = _buf.join' end end describe Temple::Generators::StringBuffer do it 'should compile simple expressions' do gen = Temple::Generators::StringBuffer.new gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:dynamic, 'test']).should.equal '_buf = (test).to_s' gen.call([:code, 'test']).should.equal '_buf = \'\'; test; _buf' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = \'\'; _buf << ("a"); _buf << ("b"); _buf' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = \'\'; _buf << ("a"); _buf << ((b).to_s); _buf' end end describe Temple::Generators::ERB do it 'should compile simple expressions' do gen = Temple::Generators::ERB.new gen.call([:static, 'test']).should.equal 'test' gen.call([:dynamic, 'test']).should.equal '<%= test %>' gen.call([:code, 'test']).should.equal '<% test %>' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal 'ab' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal 'a<%= b %>' end end describe Temple::Generators::RailsOutputBuffer do it 'should compile simple expressions' do gen = Temple::Generators::RailsOutputBuffer.new gen.call([:static, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + '@output_buffer.safe_concat(("test")); @output_buffer' gen.call([:dynamic, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + '@output_buffer.safe_concat(((test).to_s)); @output_buffer' gen.call([:code, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + 'test; @output_buffer' end end temple-0.6.6/test/test_grammar.rb0000644000004100000410000000413712200421022017020 0ustar www-datawww-datarequire 'helper' describe Temple::Grammar do it 'should match core expressions' do Temple::Grammar.should.match [:multi] Temple::Grammar.should.match [:multi, [:multi]] Temple::Grammar.should.match [:static, 'Text'] Temple::Grammar.should.match [:dynamic, 'Text'] Temple::Grammar.should.match [:code, 'Text'] Temple::Grammar.should.match [:capture, 'Text', [:multi]] Temple::Grammar.should.match [:newline] end it 'should not match invalid core expressions' do Temple::Grammar.should.not.match [:multi, 'String'] Temple::Grammar.should.not.match [:static] Temple::Grammar.should.not.match [:dynamic, 1] Temple::Grammar.should.not.match [:code, :sym] Temple::Grammar.should.not.match [:capture, [:multi]] Temple::Grammar.should.not.match [:newline, [:multi]] end it 'should match control flow expressions' do Temple::Grammar.should.match [:if, 'Condition', [:multi]] Temple::Grammar.should.match [:if, 'Condition', [:multi], [:multi]] Temple::Grammar.should.match [:block, 'Loop', [:multi]] Temple::Grammar.should.match [:case, 'Arg', ['Cond1', [:multi]], ['Cond1', [:multi]], [:else, [:multi]]] Temple::Grammar.should.not.match [:case, 'Arg', [:sym, [:multi]]] Temple::Grammar.should.match [:cond, ['Cond1', [:multi]], ['Cond2', [:multi]], [:else, [:multi]]] Temple::Grammar.should.not.match [:cond, [:sym, [:multi]]] end it 'should match escape expression' do Temple::Grammar.should.match [:escape, true, [:multi]] Temple::Grammar.should.match [:escape, false, [:multi]] end it 'should match html expressions' do Temple::Grammar.should.match [:html, :doctype, 'Doctype'] Temple::Grammar.should.match [:html, :comment, [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi], [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi], [:static, 'Text']] Temple::Grammar.should.match [:html, :tag, 'Tag', [:html, :attrs, [:html, :attr, 'id', [:static, 'val']]], [:static, 'Text']] end end temple-0.6.6/test/html/0000755000004100000410000000000012200421022014745 5ustar www-datawww-datatemple-0.6.6/test/html/test_attribute_sorter.rb0000644000004100000410000000373412200421022021741 0ustar www-datawww-datarequire 'helper' describe Temple::HTML::AttributeSorter do before do @ordered = Temple::HTML::AttributeSorter.new @unordered = Temple::HTML::AttributeSorter.new :sort_attrs => false end it 'should sort html attributes by name by default, when :sort_attrs is true' do @ordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']], [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']]]] end it 'should preserve the order of html attributes when :sort_attrs is false' do @unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]]] # Use case: @unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]]] end end temple-0.6.6/test/html/test_pretty.rb0000644000004100000410000000501512200421022017661 0ustar www-datawww-datarequire 'helper' describe Temple::HTML::Pretty do before do @html = Temple::HTML::Pretty.new end it 'should indent nested tags' do @html.call([:html, :tag, 'div', [:multi], [:html, :tag, 'p', [:multi], [:multi, [:static, 'text'], [:dynamic, 'code']]] ]).should.equal [:multi, [:code, "_temple_html_pretty1 = /"], [:multi, [:static, "\n "], [:multi, [:static, "\n text"], [:multi, [:code, "_temple_html_pretty2 = (code).to_s"], [:code, "if _temple_html_pretty1 !~ _temple_html_pretty2; _temple_html_pretty2 = _temple_html_pretty2.gsub(\"\n\", \"\\n \"); end"], [:dynamic, "_temple_html_pretty2"]]], [:static, "\n

"]], [:static, "\n"]]] end it 'should not indent preformatted tags' do @html.call([:html, :tag, 'pre', [:multi], [:html, :tag, 'p', [:multi], [:static, 'text']] ]).should.equal [:multi, [:code, "_temple_html_pretty1 = /"], [:multi, [:static, ""], [:static, "text"], [:static, "

"]], [:static, ""]]] end it 'should not escape html_safe strings' do with_html_safe do @html.call( [:dynamic, '"text<".html_safe'] ).should.equal [:multi, [:code, "_temple_html_pretty1 = /']] @html.call([:multi, [:html, :doctype, 'html']]).should.equal [:multi, [:static, '']] @html.call([:multi, [:html, :doctype, '1.1']]).should.equal [:multi, [:static, '']] end it 'should compile xml encoding' do @html.call([:html, :doctype, 'xml latin1']).should.equal [:static, ""] end it 'should compile html comment' do @html.call([:html, :comment, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should compile js wrapped in comments' do Temple::HTML::Fast.new(:js_wrapper => nil).call([:html, :js, [:static, 'test']]).should.equal [:static, "test"] Temple::HTML::Fast.new(:js_wrapper => :comment).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] Temple::HTML::Fast.new(:js_wrapper => :cdata).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, "\n//\n"]] Temple::HTML::Fast.new(:js_wrapper => :both).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should guess default js comment' do Temple::HTML::Fast.new(:js_wrapper => :guess, :format => :xhtml).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, "\n//\n"]] Temple::HTML::Fast.new(:js_wrapper => :guess, :format => :html).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should compile autoclosed html tag' do @html.call([:html, :tag, 'img', [:attrs], [:multi, [:newline]] ]).should.equal [:multi, [:static, ""], [:multi, [:newline]]] end it 'should compile explicitly closed html tag' do @html.call([:html, :tag, 'closed', [:attrs] ]).should.equal [:multi, [:static, ""]] end it 'should compile html with content' do @html.call([:html, :tag, 'div', [:attrs], [:content] ]).should.equal [:multi, [:static, ""], [:content], [:static, ""]] end it 'should compile html with attrs' do @html.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:static, 'test']], [:html, :attr, 'class', [:dynamic, 'block']]], [:content] ]).should.equal [:multi, [:static, ""], [:content], [:static, ""]] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] @html.call(exp).should.equal exp end it 'should keep statics intact' do exp = [:multi, [:static, '<']] @html.call(exp).should.equal exp end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] @html.call(exp).should.equal exp end end temple-0.6.6/test/test_engine.rb0000644000004100000410000001161112200421022016632 0ustar www-datawww-data# -*- coding: utf-8 -*- require 'helper' class Callable1 def call(exp) exp end end class Callable2 def call(exp) exp end end class TestEngine < Temple::Engine use(:Parser) do |input| [:static, input] end use :MyFilter1, proc {|exp| exp } use :MyFilter2, proc {|exp| exp } use Temple::HTML::Pretty, :format, :pretty => true filter :MultiFlattener generator :ArrayBuffer use :BeforeLast, Callable1.new use(:Last) { Callable2.new } end describe Temple::Engine do it 'should build chain' do TestEngine.chain.size.should.equal 8 TestEngine.chain[0].first.should.equal :Parser TestEngine.chain[0].size.should.equal 2 TestEngine.chain[0].last.should.be.instance_of Proc TestEngine.chain[1].first.should.equal :MyFilter1 TestEngine.chain[1].size.should.equal 2 TestEngine.chain[1].last.should.be.instance_of Proc TestEngine.chain[2].first.should.equal :MyFilter2 TestEngine.chain[2].size.should.equal 2 TestEngine.chain[2].last.should.be.instance_of Proc TestEngine.chain[3].first.should.equal :'Temple::HTML::Pretty' TestEngine.chain[3].size.should.equal 2 TestEngine.chain[3].last.should.be.instance_of Proc TestEngine.chain[4].first.should.equal :MultiFlattener TestEngine.chain[4].size.should.equal 2 TestEngine.chain[4].last.should.be.instance_of Proc TestEngine.chain[5].first.should.equal :ArrayBuffer TestEngine.chain[5].size.should.equal 2 TestEngine.chain[5].last.should.be.instance_of Proc TestEngine.chain[6].first.should.equal :BeforeLast TestEngine.chain[6].size.should.equal 2 TestEngine.chain[6].last.should.be.instance_of Proc TestEngine.chain[7].first.should.equal :Last TestEngine.chain[7].size.should.equal 2 TestEngine.chain[7].last.should.be.instance_of Proc end it 'should instantiate chain' do call_chain = TestEngine.new.send(:call_chain) call_chain[0].should.be.instance_of Method call_chain[1].should.be.instance_of Method call_chain[2].should.be.instance_of Method 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 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 8 engine.append :MyFilter3 do |exp| exp end 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 Proc call_chain = engine.send(:call_chain) 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 8 engine.prepend :MyFilter0 do |exp| exp end 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 Proc engine.chain[1].first.should.equal :Parser call_chain = engine.send(:call_chain) call_chain.size.should.equal 9 call_chain[0].should.be.instance_of Method end it 'should have #after' do engine = TestEngine.new engine.after :Parser, :MyFilter0 do |exp| exp end TestEngine.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 end it 'should have #before' do engine = TestEngine.new engine.before :MyFilter1, :MyFilter0 do |exp| exp end TestEngine.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 end it 'should have #remove' do engine = TestEngine.new engine.remove :MyFilter1 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 it 'should have #replace' do engine = TestEngine.new engine.replace :Parser, :MyParser do |exp| exp end engine.chain.size.should.equal 8 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 8 inherited_engine.append :MyFilter3 do |exp| exp end inherited_engine.chain.size.should.equal 9 TestEngine.chain.size.should.equal 8 end end temple-0.6.6/test/test_hash.rb0000644000004100000410000000173612200421022016317 0ustar www-datawww-datarequire 'helper' describe Temple::ImmutableHash do it 'has read accessor' do hash = Temple::ImmutableHash.new({:a => 1},{:b => 2, :a => 3}) hash[:a].should.equal 1 hash[:b].should.equal 2 end it 'has include?' do hash = Temple::ImmutableHash.new({:a => 1},{:b => 2, :a => 3}) hash.should.include :a hash.should.include :b hash.should.not.include :c end it 'has values' do Temple::ImmutableHash.new({:a => 1},{:b => 2, :a => 3}).values.sort.should.equal [1,2] end it 'has keys' do Temple::ImmutableHash.new({:a => 1},{:b => 2, :a => 3}).keys.should.equal [:a,:b] end it 'has to_a' do Temple::ImmutableHash.new({:a => 1},{:b => 2, :a => 3}).to_a.should.equal [[:a, 1], [:b, 2]] end end describe Temple::MutableHash do it 'has write accessor' do parent = {:a => 1} hash = Temple::MutableHash.new(parent) hash[:a].should.equal 1 hash[:a] = 2 hash[:a].should.equal 2 parent[:a].should.equal 1 end end temple-0.6.6/LICENSE0000644000004100000410000000203712200421022014031 0ustar www-datawww-dataCopyright (c) 2010 Magnus Holm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. temple-0.6.6/README.md0000644000004100000410000002126512200421022014307 0ustar www-datawww-dataTemple ====== [![Build Status](https://secure.travis-ci.org/judofyr/temple.png?branch=master)](http://travis-ci.org/judofyr/temple) [![Dependency Status](https://gemnasium.com/judofyr/temple.png?travis)](https://gemnasium.com/judofyr/temple) [![Code Climate](https://codeclimate.com/github/judofyr/temple.png)](https://codeclimate.com/github/judofyr/temple) Temple is an abstraction and a framework for compiling templates to pure Ruby. It's all about making it easier to experiment, implement and optimize template languages. If you're interested in implementing your own template language, or anything else related to the internals of a template engine: You've come to the right place. Have a look around, and if you're still wondering: Ask on the mailing list and we'll try to do our best. In fact, it doesn't have to be related to Temple at all. As long as it has something to do with template languages, we're interested: . Links ----- * Source: * Bugs: * List: * API documentation: * Latest Gem: * GitHub master: Overview -------- Temple is built on a theory that every template consists of three elements: * Static text * Dynamic text (pieces of Ruby which are evaluated and sent to the client) * Codes (pieces of Ruby which are evaluated and *not* sent to the client, but might change the control flow). The goal of a template engine is to take the template and eventually compile it into *the core abstraction*: ```ruby [:multi, [:static, "Hello "], [:dynamic, "@user.name"], [:static, "!\n"], [:code, "if @user.birthday == Date.today"], [:static, "Happy birthday!"], [:code, "end"]] ``` Then you can apply some optimizations, feed it to Temple and it generates fast Ruby code for you: ```ruby _buf = [] _buf << ("Hello #{@user.name}!\n") if @user.birthday == Date.today _buf << "Happy birthday!" end _buf.join ``` S-expression ------------ In Temple, an Sexp is simply an array (or a subclass) where the first element is the *type* and the rest are the *arguments*. The type must be a symbol and it's recommended to only use strings, symbols, arrays and numbers as arguments. Temple uses Sexps to represent templates because it's a simple and straightforward data structure, which can easily be written by hand and manipulated by computers. Some examples: ```ruby [:static, "Hello World!"] [:multi, [:static, "Hello "], [:dynamic, "@world"]] [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]] ``` *NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp` class. While you can use this class (since it's a subclass of Array), it's not what Temple mean by "Sexp". Abstractions ------------ The idea behind Temple is that abstractions are good, and it's better to have too many than too few. While you should always end up with the core abstraction, you shouldn't stress about it. Take one step at a time, and only do one thing at every step. So what's an abstraction? An abstraction is when you introduce a new types: ```ruby # Instead of: [:static, "Use the force"] # You use: [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]] ``` ### Why are abstractions so important? First of all, it means that several template engines can share code. Instead of having two engines which goes all the way to generating HTML, you have two smaller engines which only compiles to the HTML abstraction together with something that compiles the HTML abstraction to the core abstraction. Often you also introduce abstractions because there's more than one way to do it. There's not a single way to generate HTML. Should it be indented? If so, with tabs or spaces? Or should it remove as much whitespace as possible? Single or double quotes in attributes? Escape all weird UTF-8 characters? With an abstraction you can easily introduce a completely new HTML compiler, and whatever is below doesn't have to care about it *at all*. They just continue to use the HTML abstraction. Maybe you even want to write your compiler in another language? Sexps are easily serialized and if you don't mind working across processes, it's not a problem at all. All abstractions used by Temple are documented in {file:EXPRESSIONS.md EXPRESSIONS}. Compilers --------- A *compiler* is simply an object which responds a method called #call which takes one argument and returns a value. It's illegal for a compiler to mutate the argument, and it should be possible to use the same instance several times (although not by several threads at the same time). While a compiler can be any object, you very often want to structure it as a class. Temple then assumes the initializer takes an optional option hash: ```ruby class MyCompiler def initialize(options = {}) @options = options end def call(exp) # do stuff end end ``` ### Parsers In Temple, a parser is also a compiler, because a compiler is just something that takes some input and produces some output. A parser is then something that takes a string and returns an Sexp. It's important to remember that the parser *should be dumb*. No optimization, no guesses. It should produce an Sexp that is as close to the source as possible. You should invent your own abstraction. Maybe you even want to separate the parsers into several parts and introduce several abstractions on the way? ### Filters A filter is a compiler which take an Sexp and returns an Sexp. It might turn convert it one step closer to the core-abstraction, it might create a new abstraction, or it might just optimize in the current abstraction. Ultimately, it's still just a compiler which takes an Sexp and returns an Sexp. For instance, Temple ships with {Temple::Filters::DynamicInliner} and {Temple::Filters::StaticMerger} which are general optimization filters which works on the core abstraction. An HTML compiler would be a filter, since it would take an Sexp in the HTML abstraction and compile it down to the core abstraction. ### Generators A generator is a compiler which takes an Sexp and returns a string which is valid Ruby code. Most of the time you would just use {Temple::Generators::ArrayBuffer} or any of the other generators in {Temple::Generators}, but nothing stops you from writing your own. In fact, one of the great things about Temple is that if you write a new generator which turns out to be a lot faster then the others, it's going to make *every single engine* based on Temple faster! So if you have any ideas, please share them - it's highly appreciated. Engines ------- When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy: ```ruby class MyEngine < Temple::Engine # First run MyParser, passing the :strict option use MyParser, :strict # Then a custom filter use MyFilter # Then some general optimizations filters filter :MultiFlattener filter :StaticMerger filter :DynamicInliner # Finally the generator generator :ArrayBuffer, :buffer end engine = MyEngine.new(:strict => "For MyParser") engine.call(something) ``` And then? --------- You've ran the template through the parser, some filters and in the end a generator. What happens next? Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and Rails. ```ruby require 'tilt' # Create template class MyTemplate and register your file extension MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext') Tilt.new('example.ext').render # => Render a file MyTemplate.new { "String" }.render # => Render a string ``` Installation ------------ You need Ruby 1.8.7 or Ruby 1.9.2 to work with Temple. Temple is published as a Ruby Gem which can be installed as following: ```bash $ gem install temple ``` Engines using Temple -------------------- * [Slim](http://github.com/slim-template/slim) * [Sal](http://github.com/stonean/sal.rb) * [Temple-Mustache (Example implementation)](https://github.com/minad/temple-mustache) * Temple ERB example implementation (Temple::ERB::Template) Acknowledgements ---------------- Thanks to [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) for creating an excellent template engine (Markaby) which is quite slow. That's how I started experimenting with template engines in the first place. I also owe [Ryan Davis](http://zenspider.com/) a lot for his excellent projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is heavily inspired by how these tools work. temple-0.6.6/Rakefile0000644000004100000410000000100712200421022014465 0ustar www-datawww-datarequire 'rake/testtask' task :default => :test task :test do sh "bacon -Ilib -Itest --automatic --quiet" end #Rake::TestTask.new(:test) do |t| # t.libs << 'lib' << 'test' # t.pattern = 'test/**/test_*.rb' # t.verbose = false #end begin require 'rcov/rcovtask' Rcov::RcovTask.new do |t| t.libs << 'lib' << 'test' t.pattern = 'test/**/test_*.rb' t.verbose = false end rescue LoadError task :rcov do abort "RCov is not available. In order to run rcov, you must: gem install rcov" end end temple-0.6.6/checksums.yaml.gz0000444000004100000410000000041412200421022016307 0ustar www-datawww-data‹lIøQe+²@ ýžb/°Ô$“ïs8<'ÈäƒÂP«8=Ó RN¿^¯Ç÷o_áãñ|þìwT¼ãËßOÍĪêÌ’)±:WcßÁ¥s:÷ºÜ'óŽ_Ÿœoñê&A.±Ê¬.ŽÚÒ% ‚‚‡#þZð?ï1•èF6Ïít·eì̤"Ûƒ6$;ê$…A9â!òñÕ.' Q;G‡âxZìµ¢‹g»2w/å>Ó¤¡5 F+áßó5xÏ.¯m&} @Æ[ a ê¬}Ƀ½»ÁéúTQc8 ލu-^°Ãn¥¡í}ôH‘âœuƒºB fy©L?þ MÒX¢temple-0.6.6/EXPRESSIONS.md0000644000004100000410000001641212200421022015172 0ustar www-datawww-dataTemple expression documentation =============================== Temple uses S-expressions to represent the parsed template code. The S-expressions are passed from filter to filter until the generator. The generator transforms the S-expression to a ruby code string. See the {file:README.md README} for an introduction. In this document we documented all the expressions which are used by Temple. There is also a formal grammar which can validate expressions. The Core Abstraction -------------------- The core abstraction is what every template evetually should be compiled to. Currently it consists of six types: multi, static, dynamic, code, newline and capture. When compiling, there's two different strings we'll have to think about. First we have the generated code. This is what your engine (from Temple's point of view) spits out. If you construct this carefully enough, you can make exceptions report correct line numbers, which is very convenient. Then there's the result. This is what your engine (from the user's point of view) spits out. It's what happens if you evaluate the generated code. ### [:multi, *sexp] Multi is what glues everything together. It's simply a sexp which combines several others sexps: [:multi, [:static, "Hello "], [:dynamic, "@world"]] ### [:static, string] Static indicates that the given string should be appended to the result. Example: [:static, "Hello World"] is the same as: _buf << "Hello World" [:static, "Hello \n World"] is the same as _buf << "Hello\nWorld" ### [:dynamic, ruby] Dynamic indicates that the given Ruby code should be evaluated and then appended to the result. The Ruby code must be a complete expression in the sense that you can pass it to eval() and it would not raise SyntaxError. Example: [:dynamic, 'Math::PI * r**2'] ### [:code, ruby] Code indicates that the given Ruby code should be evaluated, and may change the control flow. Any \n causes a newline in the generated code. Example: [:code, 'area = Math::PI * r**2'] ### [:newline] Newline causes a newline in the generated code, but not in the result. ### [:capture, variable_name, sexp] Evaluates the Sexp using the rules above, but instead of appending to the result, it sets the content to the variable given. Example: [:multi, [:static, "Some content"], [:capture, "foo", [:static, "More content"]], [:dynamic, "foo.downcase"]] is the same as: _buf << "Some content" foo = "More content" _buf << foo.downcase Control flow abstraction ------------------------ Control flow abstractions can be used to write common ruby control flow constructs. These expressions are compiled to [:code, ruby] by Temple::Filters::ControlFlow ### [:if, condition, if-sexp, optional-else-sexp] Example: [:if, "1+1 == 2", [:static, "Yes"], [:static, "No"]] is the same as: if 1+1 == 2 _buf << "Yes" else _buf << "No" end ### [:block, ruby, sexp] Example: [:block, '10.times do', [:static, 'Hello']] is the same as: 10.times do _buf << 'Hello' end ### [:case, argument, [condition, sexp], [condition, sexp], ...] Example: [:case, 'value', ["1", "value is 1"], ["2", "value is 2"], [:else, "don't know"]] is the same as: case value when 1 _buf << "value is 1" when 2 _buf << "value is 2" else _buf << "don't know" end ### [:cond, [condition, sexp], [condition, sexp], ...] [:cond, ["a", "a is true"], ["b", "b is true"], [:else, "a and b are false"]] is the same as: case when a _buf << "a is true" when b _buf << "b is true" else _buf << "a and b are false" end Escape abstraction ------------------ The Escape abstraction is processed by Temple::Filters::Escapable. ### [:escape, bool, sexp] The boolean flag switches escaping on or off for the content sexp. Dynamic and static expressions are manipulated. Example: [:escape, true, [:multi, [:dynamic, "code"], [:static, "<"], [:escape, false, [:static, ">"]]]] is transformed to [:multi, [:dynamic, 'escape_html(code)'], [:static, '<'], [:static, '>']] HTML abstraction ---------------- The HTML abstraction is processed by the html filters (Temple::HTML::Fast and Temple::HTML::Pretty). ### [:html, :doctype, string] Example: [:html, :doctype, '5'] generates Supported doctypes:
NameGenerated doctype
1.1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
html, 5<!DOCTYPE html>
strict<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
frameset<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
mobile<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
basic<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
transitional<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
### [:html, :comment, sexp] Example: [:html, :comment, [:static, 'comment']] generates: ### [:html, :condcomment, condition, sexp] Example: [:html, :condcomment, 'IE', [:static, 'comment']] generates: ### [:html, :tag, identifier, attributes, optional-sexp] HTML tag abstraction. Identifier can be a String or a Symbol. If the optional content Sexp is omitted the tag is closed (e.g.
). The tag is also closed if the content Sexp is empty (consists only of :multi and :newline expressions) and the tag is registered as auto-closing. Example: [:html, :tag, 'img', [:html, :attrs, [:html, :attr, 'src', 'image.png']]] [:html, :tag, 'p', [:multi], [:static, 'Content']] generates:

Content

### [:html, :attrs, attributes] List of html attributes [:html, :attr, identifier, sexp] ### [:html, :attr, identifier, sexp] HTML attribute abstraction. Identifier can be a String or a Symbol. ### [:html, :js, code] HTML javascript abstraction which wraps the js code in a HTML comment or CDATA depending on document format. Formal grammar -------------- Validate expressions with Temple::Grammar.match? and Temple::Grammar.validate! Temple::Grammar.match? [:multi, [:static, 'Valid Temple Expression']] Temple::Grammar.validate! [:multi, 'Invalid Temple Expression'] The formal grammar is given in a Ruby DSL similar to EBNF and should be easy to understand if you know EBNF. Repeated tokens are given by appending ?, * or + as in regular expressions. * ? means zero or one occurence * \* means zero or more occurences * \+ means one or more occurences temple-0.6.6/metadata.yml0000644000004100000410000001132312200421022015325 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: temple version: !ruby/object:Gem::Version version: 0.6.6 platform: ruby authors: - Magnus Holm - Daniel Mendler autorequire: bindir: bin cert_chain: [] date: 2013-07-31 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: tilt requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: bacon requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' description: email: - judofyr@gmail.com - mail@daniel-mendler.de executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .travis.yml - .yardopts - CHANGES - EXPRESSIONS.md - Gemfile - LICENSE - README.md - Rakefile - kill-travis.sh - lib/temple.rb - lib/temple/engine.rb - lib/temple/erb/engine.rb - lib/temple/erb/parser.rb - lib/temple/erb/template.rb - lib/temple/erb/trimming.rb - lib/temple/exceptions.rb - lib/temple/filter.rb - lib/temple/filters/code_merger.rb - lib/temple/filters/control_flow.rb - lib/temple/filters/dynamic_inliner.rb - lib/temple/filters/encoding.rb - lib/temple/filters/eraser.rb - lib/temple/filters/escapable.rb - lib/temple/filters/multi_flattener.rb - lib/temple/filters/remove_bom.rb - lib/temple/filters/static_merger.rb - lib/temple/filters/validator.rb - lib/temple/generator.rb - lib/temple/generators/array.rb - lib/temple/generators/array_buffer.rb - lib/temple/generators/erb.rb - lib/temple/generators/rails_output_buffer.rb - lib/temple/generators/string_buffer.rb - lib/temple/grammar.rb - lib/temple/hash.rb - lib/temple/html/attribute_merger.rb - lib/temple/html/attribute_remover.rb - lib/temple/html/attribute_sorter.rb - lib/temple/html/dispatcher.rb - lib/temple/html/fast.rb - lib/temple/html/filter.rb - lib/temple/html/pretty.rb - lib/temple/mixins/dispatcher.rb - lib/temple/mixins/engine_dsl.rb - lib/temple/mixins/grammar_dsl.rb - lib/temple/mixins/options.rb - lib/temple/mixins/template.rb - lib/temple/parser.rb - lib/temple/templates.rb - lib/temple/templates/rails.rb - lib/temple/templates/tilt.rb - lib/temple/utils.rb - lib/temple/version.rb - temple.gemspec - test/filters/test_code_merger.rb - test/filters/test_control_flow.rb - test/filters/test_dynamic_inliner.rb - test/filters/test_eraser.rb - test/filters/test_escapable.rb - test/filters/test_multi_flattener.rb - test/filters/test_static_merger.rb - test/helper.rb - test/html/test_attribute_merger.rb - test/html/test_attribute_remover.rb - test/html/test_attribute_sorter.rb - test/html/test_fast.rb - test/html/test_pretty.rb - test/mixins/test_dispatcher.rb - test/mixins/test_grammar_dsl.rb - test/test_engine.rb - test/test_erb.rb - test/test_filter.rb - test/test_generator.rb - test/test_grammar.rb - test/test_hash.rb - test/test_utils.rb homepage: https://github.com/judofyr/temple licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: Template compilation framework in Ruby test_files: - test/filters/test_code_merger.rb - test/filters/test_control_flow.rb - test/filters/test_dynamic_inliner.rb - test/filters/test_eraser.rb - test/filters/test_escapable.rb - test/filters/test_multi_flattener.rb - test/filters/test_static_merger.rb - test/helper.rb - test/html/test_attribute_merger.rb - test/html/test_attribute_remover.rb - test/html/test_attribute_sorter.rb - test/html/test_fast.rb - test/html/test_pretty.rb - test/mixins/test_dispatcher.rb - test/mixins/test_grammar_dsl.rb - test/test_engine.rb - test/test_erb.rb - test/test_filter.rb - test/test_generator.rb - test/test_grammar.rb - test/test_hash.rb - test/test_utils.rb temple-0.6.6/.yardopts0000644000004100000410000000006612200421022014672 0ustar www-datawww-data--title Temple --files EXPRESSIONS.md,CHANGES,LICENSE temple-0.6.6/Gemfile0000644000004100000410000000005012200421022014310 0ustar www-datawww-datasource 'https://rubygems.org/' gemspec temple-0.6.6/CHANGES0000644000004100000410000001022312200421022014013 0ustar www-datawww-datamaster * Use default encoding utf-8 * Escape also ' * Try to load escape_utils by default 0.6.5 * Added Filters::CodeMerger * Added Filters::Encoding * Added Filters::RemoveBOM * Added Generators::ERB 0.6.4 * Check for ActionView instead of Rails (#72) 0.6.3 * Fix HTML escaping for HTML::Pretty (Issue #69) 0.6.2 * [:html, :js, code] abstraction added 0.6.1 * HTML::Pretty improved 0.6.0 * HTML::AttributeMerger: rename option :attr_delimiter to :merge_attrs * HTML: rename option :attr_wrapper to :attr_quote 0.5.5 * HTML pretty: Do not remove empty lines, add newline after doctype 0.5.4 * HTML::AttributeMerger fixed, it didn't remove first empty attribute values * Add HTML::AttributeRemover back, :remove_empty_attrs must be an Array of Strings now of the attributes to be removed if empty * Simplify [:case] expression grammar * Ignore parameter :outvar by sinatra since sinatra assumes also that the buffer is a String, they should set :buffer and :generator explicitly if they need the access 0.5.3 * Only print an message if invalid options are passed to Temple filters or engines since many libraries seem to use Slim and Temple in an incorrect way 0.5.2 * Fix the :outvar problem really 0.5.1 * Support Sinatra :outvar option in Tilt template 0.5.0 * Added exception Temple::FilterError which should be thrown by filters * Added Temple::Parser as default base class for parsers * escape_html doesn't escape / anymore * HTML::AttributeSorter uses stable sorting now * HTML::AttributeRemover removed (Was too Slim specific) * Engine option :chain removed * Option validation implemented (Use define_options in your filters) * Deprecated options implemented (Use deprecated_options in your filters) * ThreadOptions added, Method #with_options 0.4.1 * Generators: produce optimized code * remove deprecated method EngineDSL#wildcard * Set tilt template default_mime_type to text/html * HTML: Support conditional comments [:html, :condcomment, ...] 0.4.0 * Split Temple::HTML::AttributeMerger in AttributeSorter, AttributeMerger and AttributeRemover * Fix issue #58 0.3.5 * Temple::HTML::Pretty improved * :sort_attrs option (default: true) added to HTML::AttributeMerger; if set to false, the attributes will appear in the insertion order * Temple::Mixins::EngineDSL api changed ("wildcard" is deprecated, use "use" instead) * Temple::Mixins::CompiledDispatcher supports arbitrary levels now * Don't use gsub! on incoming strings (#57) * Fix newlines in erb parser (#46) 0.3.4 * Bugfix release (0.3.3 was yanked) 0.3.3 * Support for rails 3.1 streaming * Add EngineDSL#wildcard * HTML::Fast/Pretty supports only :xhtml and :html formats from now on * HTML::AttributeMerger extracted from HTML::Fast 0.3.1, 0.3.2 * Don't modify strings destructively with gsub! in HTML::Pretty. This doesn't work with Rails safe buffers in version >= 3.0.8. 0.3.0 * Compiled expression dispatching * Method temple_dispatch is obsolete * EscapeHTML renamed to Escapable * Control flow filter added * HTML filter: Tag and attribute expressions changed * Expression grammar added * Expression validator added * Debugger filter removed (Validator is better replacement) 0.2.0 * Add mutable/immutable hashes for option inheritance * Rails template support added * Rename Filter#compile to Filter#call * Engine chain reconfiguration (append, prepend, replace, ...) * HTML filter: Don't output empty attributes * Escape expression changed [:escape, true/false, Expression] 0.1.8 * HTML filter: Support :format => :html (alias for :html5) 0.1.7 * HTML::Pretty indents dynamic content only if it doesn't contain preformatted tags 0.1.6 * Flexible chain building 0.1.5 * Default options for engines 0.1.4 * HTML::Pretty added * Tilt-based template class added * Escaping filter added * Filter base class added * Fix capturing (Issue #15) 0.1.3 * Close issue #10 * Refactoring 0.1.2 * Add HTML filter * Remove Escapable filter * Add method for checking if expression is empty 0.1.1 * Test added 0.1.0 * Initial release temple-0.6.6/.gitignore0000644000004100000410000000004212200421022015006 0ustar www-datawww-datacoverage .yardoc doc Gemfile.lock temple-0.6.6/lib/0000755000004100000410000000000012200421022013570 5ustar www-datawww-datatemple-0.6.6/lib/temple/0000755000004100000410000000000012200421022015056 5ustar www-datawww-datatemple-0.6.6/lib/temple/templates.rb0000644000004100000410000000040512200421022017400 0ustar www-datawww-datamodule Temple # @api public module Templates autoload :Tilt, 'temple/templates/tilt' autoload :Rails, 'temple/templates/rails' def self.method_missing(name, engine, options = {}) const_get(name).create(engine, options) end end end temple-0.6.6/lib/temple/generator.rb0000644000004100000410000000232312200421022017371 0ustar www-datawww-datamodule Temple # Abstract generator base class # Generators should inherit this class and # compile the Core Abstraction to ruby code. # # @api public class Generator include Mixins::CompiledDispatcher include Mixins::Options define_options :capture_generator => 'StringBuffer', :buffer => '_buf' def call(exp) [preamble, compile(exp), postamble].join('; ') end def on(*exp) raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}" end def on_multi(*exp) exp.map {|e| compile(e) }.join('; ') end def on_newline "\n" end def on_capture(name, exp) capture_generator.new(:buffer => name).call(exp) end def on_static(text) concat(text.inspect) end def on_dynamic(code) concat(code) end def on_code(code) code end protected def buffer options[:buffer] end def capture_generator @capture_generator ||= Class === options[:capture_generator] ? options[:capture_generator] : Generators.const_get(options[:capture_generator]) end def concat(str) "#{buffer} << (#{str})" end end end temple-0.6.6/lib/temple/filter.rb0000644000004100000410000000023312200421022016666 0ustar www-datawww-datamodule Temple # Temple base filter # @api public class Filter include Utils include Mixins::Dispatcher include Mixins::Options end end temple-0.6.6/lib/temple/mixins/0000755000004100000410000000000012200421022016365 5ustar www-datawww-datatemple-0.6.6/lib/temple/mixins/options.rb0000644000004100000410000000472112200421022020411 0ustar www-datawww-datamodule Temple module Mixins # @api public module DefaultOptions def set_default_options(opts) default_options.update(opts) end def default_options @default_options ||= OptionHash.new(superclass.respond_to?(:default_options) ? superclass.default_options : nil) do |hash, key, deprecated| unless @option_validator_disabled if deprecated warn "Option #{key.inspect} is deprecated by #{self}" else # TODO: This will raise an exception in the future! # raise ArgumentError, "Option #{key.inspect} is not supported by #{self}" warn "Option #{key.inspect} is not supported by #{self}" end end end end def define_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash default_options.add_valid_keys(hash.keys) default_options.update(hash) end default_options.add_valid_keys(opts) end def define_deprecated_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash default_options.add_deprecated_keys(hash.keys) default_options.update(hash) end default_options.add_deprecated_keys(opts) end def disable_option_validator! @option_validator_disabled = true end end module ThreadOptions def with_options(options) old_options = thread_options Thread.current[thread_options_key] = ImmutableHash.new(options, thread_options) yield ensure Thread.current[thread_options_key] = old_options end def thread_options Thread.current[thread_options_key] end protected def thread_options_key @thread_options_key ||= "#{self.name}-thread-options".to_sym end end # @api public module Options def self.included(base) base.class_eval do extend DefaultOptions extend ThreadOptions end end attr_reader :options def initialize(opts = {}) self.class.default_options.validate_hash!(opts) self.class.default_options.validate_hash!(self.class.thread_options) if self.class.thread_options @options = ImmutableHash.new({}.update(self.class.default_options).update(self.class.thread_options || {}).update(opts)) end end end end temple-0.6.6/lib/temple/mixins/engine_dsl.rb0000644000004100000410000001206412200421022021024 0ustar www-datawww-datamodule Temple module Mixins # @api private module EngineDSL def chain_modified! end def append(*args, &block) chain << chain_element(args, block) chain_modified! end def prepend(*args, &block) chain.unshift(chain_element(args, block)) chain_modified! end def remove(name) name = chain_name(name) found = false chain.reject! do |i| if i.first == name found = true else false end end raise "#{name} not found" unless found chain_modified! end alias use append def before(name, *args, &block) name = chain_name(name) e = chain_element(args, block) found, i = false, 0 while i < chain.size if chain[i].first == name found = true chain.insert(i, e) i += 2 else i += 1 end end raise "#{name} not found" unless found chain_modified! end def after(name, *args, &block) name = chain_name(name) e = chain_element(args, block) found, i = false, 0 while i < chain.size if chain[i].first == name found = true i += 1 chain.insert(i, e) end i += 1 end raise "#{name} not found" unless found chain_modified! end def replace(name, *args, &block) name = chain_name(name) e = chain_element(args, block) found = false chain.each_with_index do |c, i| if c.first == name found = true chain[i] = e end end raise "#{name} not found" unless found chain_modified! end # Shortcuts to access namespaces { :filter => Temple::Filters, :generator => Temple::Generators, :html => Temple::HTML }.each do |method, mod| define_method(method) do |name, *options| use(name, mod.const_get(name), *options) end end private def chain_name(name) name = Class === name ? name.name.to_sym : name raise(ArgumentError, 'Name argument must be Class or Symbol') unless Symbol === name name end def chain_class_constructor(filter, option_filter) local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {} raise(ArgumentError, 'Only symbols allowed in option filter') unless option_filter.all? {|o| Symbol === o } define_options(*option_filter) if respond_to?(:define_options) proc do |engine| filter.new({}.update(engine.options).delete_if {|k,v| !option_filter.include?(k) }.update(local_options)) end end def chain_proc_constructor(name, filter) raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1 method_name = "FILTER #{name}" if Class === self define_method(method_name, &filter) filter = instance_method(method_name) if filter.arity == 1 proc {|engine| filter.bind(engine) } else proc do |engine| f = filter.bind(engine).call raise 'Constructor must return callable object' unless f.respond_to?(:call) f end end else (class << self; self; end).class_eval { define_method(method_name, &filter) } filter = method(method_name) proc {|engine| filter } end end def chain_callable_constructor(filter) raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call) proc {|engine| filter } end def chain_element(args, block) name = args.shift if Class === name filter = name name = filter.name.to_sym else raise(ArgumentError, 'Name argument must be Class or Symbol') unless Symbol === name end if block raise(ArgumentError, 'Class and block argument are not allowed at the same time') if filter filter = block end filter ||= args.shift case filter when Proc # Proc or block argument # The proc is converted to a method of the engine class. # The proc can then access the option hash of the engine. raise(ArgumentError, 'Too many arguments') unless args.empty? [name, chain_proc_constructor(name, filter)] when Class # Class argument (e.g Filter class) # The options are passed to the classes constructor. [name, chain_class_constructor(filter, args)] 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? [name, chain_callable_constructor(filter)] end end end end end temple-0.6.6/lib/temple/mixins/grammar_dsl.rb0000644000004100000410000001000212200421022021173 0ustar www-datawww-datamodule Temple module Mixins # @api private module GrammarDSL class Rule def initialize(grammar) @grammar = grammar end def |(rule) Or.new(@grammar, self, rule) end def copy_to(grammar) copy = dup.instance_eval { @grammar = grammar; self } copy.after_copy(self) if copy.respond_to?(:after_copy) copy end end class Or < Rule def initialize(grammar, *children) super(grammar) @children = children.map {|rule| @grammar.Rule(rule) } end def <<(rule) @children << @grammar.Rule(rule) self end alias | << def match(exp, unmatched) tmp = [] @children.any? {|rule| rule.match(exp, tmp) } || (unmatched.concat(tmp) && false) end def after_copy(source) @children = @children.map {|child| child.copy_to(@grammar) } end end class Root < Or def initialize(grammar, name) super(grammar) @name = name.to_sym end def match(exp, unmatched) success = super unmatched << [@name, exp] unless success success end def match?(exp) match(exp, []) end def validate!(exp) unmatched = [] unless match(exp, unmatched) require 'pp' entry = unmatched.first unmatched.reverse_each do |u| entry = u if u.flatten.size < entry.flatten.size end raise(InvalidExpression, PP.pp(entry.last, "#{@grammar}::#{entry.first} did not match\n")) end end def copy_to(grammar) grammar.const_defined?(@name) ? grammar.const_get(@name) : super end def after_copy(source) @grammar.const_set(@name, self) super end end class Element < Or def initialize(grammar, rule) super(grammar) @rule = grammar.Rule(rule) end def match(exp, unmatched) return false unless Array === exp && !exp.empty? head, *tail = exp @rule.match(head, unmatched) && super(tail, unmatched) end def after_copy(source) @children = @children.map do |child| child == source ? self : child.copy_to(@grammar) end @rule = @rule.copy_to(@grammar) end end class Value < Rule def initialize(grammar, value) super(grammar) @value = value end def match(exp, unmatched) @value === exp end end def extended(mod) mod.extend GrammarDSL constants.each do |name| const_get(name).copy_to(mod) if Rule === const_get(name) end end def match?(exp) const_get(:Expression).match?(exp) end def validate!(exp) const_get(:Expression).validate!(exp) end alias === match? alias =~ match? def Value(value) Value.new(self, value) end def Rule(rule) case rule when Rule rule when Symbol, Class, true, false, nil Value(rule) when Array start = Or.new(self) curr = [start] rule.each do |elem| if elem =~ /^(.*)(\*|\?|\+)$/ elem = Element.new(self, const_get($1)) curr.each {|c| c << elem } elem << elem if $2 != '?' curr = $2 == '+' ? [elem] : (curr << elem) else elem = Element.new(self, elem) curr.each {|c| c << elem } curr = [elem] end end elem = Value([]) curr.each {|c| c << elem } start else raise ArgumentError, "Invalid grammar rule '#{rule.inspect}'" end end def const_missing(name) const_set(name, Root.new(self, name)) end end end end temple-0.6.6/lib/temple/mixins/dispatcher.rb0000644000004100000410000001062512200421022021044 0ustar www-datawww-datamodule Temple module Mixins # @api private module CoreDispatcher def on_multi(*exps) multi = [:multi] exps.each {|exp| multi << compile(exp) } multi end def on_capture(name, exp) [:capture, name, compile(exp)] end end # @api private module EscapeDispatcher def on_escape(flag, exp) [:escape, flag, compile(exp)] end end # @api private module ControlFlowDispatcher def on_if(condition, *cases) [:if, condition, *cases.compact.map {|e| compile(e) }] end def on_case(arg, *cases) [:case, arg, *cases.map {|condition, exp| [condition, compile(exp)] }] end def on_block(code, content) [:block, code, compile(content)] end def on_cond(*cases) [:cond, *cases.map {|condition, exp| [condition, compile(exp)] }] end end # @api private module CompiledDispatcher def call(exp) compile(exp) end def compile(exp) dispatcher(exp) end private def dispatcher(exp) replace_dispatcher(exp) end def replace_dispatcher(exp) tree = DispatchNode.new dispatched_methods.each do |method| method.split('_')[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method end self.class.class_eval %{def dispatcher(exp) return replace_dispatcher(exp) if self.class != #{self.class} #{tree.compile.gsub("\n", "\n ")} end} dispatcher(exp) end def dispatched_methods re = /^on(_[a-zA-Z0-9]+)*$/ self.methods.map(&:to_s).select(&re.method(:=~)) end # @api private class DispatchNode < Hash attr_accessor :method def initialize super { |hsh,key| hsh[key] = DispatchNode.new } @method = nil end def compile(level = 0, call_parent = nil) call_method = method ? (level == 0 ? "#{method}(*exp)" : "#{method}(*exp[#{level}..-1])") : call_parent if empty? raise 'Invalid dispatcher node' unless method call_method else code = "case(exp[#{level}])\n" each do |key, child| code << "when #{key.inspect}\n " << child.compile(level + 1, call_method).gsub("\n", "\n ") << "\n" end code << "else\n " << (call_method || 'exp') << "\nend" end end end end # @api public # # Implements a compatible call-method # based on the including classe's methods. # # It uses every method starting with # "on" and uses the rest of the method # name as prefix of the expression it # will receive. So, if a dispatcher # has a method named "on_x", this method # will be called with arg0,..,argN # whenever an expression like [:x, arg0,..,argN ] # is encountered. # # This works with longer prefixes, too. # For example a method named "on_y_z" # will be called whenever an expression # like [:y, :z, .. ] is found. Furthermore, # if additionally a method named "on_y" # is present, it will be called when an # expression starts with :y but then does # not contain with :z. This way a # dispatcher can implement namespaces. # # @note # Processing does not reach into unknown # expression types by default. # # @example # class MyAwesomeDispatch # include Temple::Mixins::Dispatcher # def on_awesome(thing) # keep awesome things # return [:awesome, thing] # end # def on_boring(thing) # make boring things awesome # return [:awesome, thing+" with bacon"] # end # def on(type,*args) # unknown stuff is boring too # return [:awesome, 'just bacon'] # end # end # filter = MyAwesomeDispatch.new # # Boring things are converted: # filter.call([:boring, 'egg']) #=> [:awesome, 'egg with bacon'] # # Unknown things too: # filter.call([:foo]) #=> [:awesome, 'just bacon'] # # Known but not boring things won't be touched: # filter.call([:awesome, 'chuck norris']) #=>[:awesome, 'chuck norris'] # module Dispatcher include CompiledDispatcher include CoreDispatcher include EscapeDispatcher include ControlFlowDispatcher end end end temple-0.6.6/lib/temple/mixins/template.rb0000644000004100000410000000131312200421022020523 0ustar www-datawww-datamodule Temple module Mixins # @api private module Template include DefaultOptions def compile(code, options) engine = options.delete(:engine) raise 'No engine configured' unless engine engine.new(options).call(code) end def register_as(*names) raise NotImplementedError end def create(engine, options) register_as = options.delete(:register_as) template = Class.new(self) template.disable_option_validator! template.default_options[:engine] = engine template.default_options.update(options) template.register_as(*register_as) if register_as template end end end end temple-0.6.6/lib/temple/grammar.rb0000644000004100000410000000317512200421022017037 0ustar www-datawww-datamodule Temple # Temple expression grammar which can be used to validate Temple expressions. # # Example: # Temple::Grammar.match? [:static, 'Valid Temple Expression'] # Temple::Grammar.validate! [:multi, 'Invalid Temple Expression'] # # See {file:EXPRESSIONS.md Expression documentation}. # # @api public module Grammar extend Mixins::GrammarDSL Expression << # Core abstraction [:multi, 'Expression*'] | [:static, String] | [:dynamic, String] | [:code, String] | [:capture, String, Expression] | [:newline] | # Control flow abstraction [:if, String, Expression, 'Expression?'] | [:block, String, Expression] | [:case, String, 'Case*'] | [:cond, 'Case*'] | # Escape abstraction [:escape, Bool, Expression] | # HTML abstraction [:html, :doctype, String] | [:html, :comment, Expression] | [:html, :condcomment, String, Expression]| [:html, :js, Expression] | [:html, :tag, HTMLIdentifier, Expression, 'Expression?'] | [:html, :attrs, 'HTMLAttr*'] | HTMLAttr EmptyExp << [:newline] | [:multi, 'EmptyExp*'] HTMLAttr << [:html, :attr, HTMLIdentifier, Expression] HTMLIdentifier << Symbol | String Case << [Condition, Expression] Condition << String | :else Bool << true | false end end temple-0.6.6/lib/temple/version.rb0000644000004100000410000000004612200421022017070 0ustar www-datawww-datamodule Temple VERSION = '0.6.6' end temple-0.6.6/lib/temple/utils.rb0000644000004100000410000000465312200421022016553 0ustar www-datawww-databegin require 'escape_utils' rescue LoadError # Loading EscapeUtils failed end module Temple # @api public module Utils extend self # Returns an escaped copy of `html`. # Strings which are declared as html_safe are not escaped. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html_safe(html) html.html_safe? ? html : escape_html(html) end if defined?(EscapeUtils) # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) EscapeUtils.escape_html(html.to_s, false) end else # Used by escape_html # @api private ESCAPE_HTML = { '&' => '&', '"' => '"', '\'' => ''', '<' => '<', '>' => '>' }.freeze if //.respond_to?(:encoding) ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) else # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise # TODO doesn't apply to jruby, so a better condition above might be preferable? ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n end if RUBY_VERSION > '1.9' # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML) end else # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) html.to_s.gsub(ESCAPE_HTML_PATTERN) {|c| ESCAPE_HTML[c] } end end end # Generate unique variable name # # @param prefix [String] Variable name prefix # @return [String] Variable name def unique_name(prefix = nil) @unique_name ||= 0 prefix ||= (@unique_prefix ||= self.class.name.gsub('::', '_').downcase) "_#{prefix}#{@unique_name += 1}" end # Check if expression is empty # # @param exp [Array] Temple expression # @return true if expression is empty def empty_exp?(exp) case exp[0] when :multi exp[1..-1].all? {|e| empty_exp?(e) } when :newline true else false end end end end temple-0.6.6/lib/temple/filters/0000755000004100000410000000000012200421022016526 5ustar www-datawww-datatemple-0.6.6/lib/temple/filters/multi_flattener.rb0000644000004100000410000000104312200421022022247 0ustar www-datawww-datamodule Temple module Filters # Flattens nested multi expressions # # @api public class MultiFlattener < Filter def on_multi(*exps) # If the multi contains a single element, just return the element return compile(exps.first) if exps.size == 1 result = [:multi] exps.each do |exp| exp = compile(exp) if exp.first == :multi result.concat(exp[1..-1]) else result << exp end end result end end end end temple-0.6.6/lib/temple/filters/validator.rb0000644000004100000410000000044312200421022021041 0ustar www-datawww-datamodule Temple module Filters # Validates temple expression with given grammar # # @api public class Validator < Filter define_options :grammar => Temple::Grammar def compile(exp) options[:grammar].validate!(exp) exp end end end end temple-0.6.6/lib/temple/filters/control_flow.rb0000644000004100000410000000222212200421022021560 0ustar www-datawww-datamodule Temple module Filters # Control flow filter which processes [:if, condition, yes-exp, no-exp] # and [:block, code, content] expressions. # This is useful for ruby code generation with lots of conditionals. # # @api public class ControlFlow < Filter def on_if(condition, yes, no = nil) result = [:multi, [:code, "if #{condition}"], compile(yes)] while no && no.first == :if result << [:code, "elsif #{no[1]}"] << compile(no[2]) no = no[3] end result << [:code, 'else'] << compile(no) if no result << [:code, 'end'] result end def on_case(arg, *cases) result = [:multi, [:code, arg ? "case (#{arg})" : 'case']] cases.map do |c| condition, exp = c result << [:code, condition == :else ? 'else' : "when #{condition}"] << compile(exp) end result << [:code, 'end'] result end def on_cond(*cases) on_case(nil, *cases) end def on_block(code, exp) [:multi, [:code, code], compile(exp), [:code, 'end']] end end end end temple-0.6.6/lib/temple/filters/dynamic_inliner.rb0000644000004100000410000000423512200421022022223 0ustar www-datawww-datamodule Temple module Filters # Inlines several static/dynamic into a single dynamic. # # @api public class DynamicInliner < Filter def on_multi(*exps) result = [:multi] curr = nil prev = [] state = :looking exps.each do |exp| type, arg = exp case type when :newline if state == :looking # We haven't found any static/dynamic, so let's just add it result << exp else # We've found something, so let's make sure the generated # dynamic contains a newline by escaping a newline and # starting a new string: # # "Hello "\ # "#{@world}" prev << exp curr[1] << "\"\\\n\"" end when :dynamic, :static case state when :looking # Found a single static/dynamic. We don't want to turn this # into a dynamic yet. Instead we store it, and if we find # another one, we add both then. state = :single prev = [exp] curr = [:dynamic, '"'] when :single # Yes! We found another one. Add the current dynamic to the result. state = :several result << curr end curr[1] << (type == :static ? arg.inspect[1..-2] : "\#{#{arg}}") else if state != :looking # We need to add the closing quote. curr[1] << '"' # If we found a single exp last time, let's add it. result.concat(prev) if state == :single end # Compile the current exp result << compile(exp) # Now we're looking for more! state = :looking end end if state != :looking # We need to add the closing quote. curr[1] << '"' # If we found a single exp last time, let's add it. result.concat(prev) if state == :single end result.size == 2 ? result[1] : result end end end end temple-0.6.6/lib/temple/filters/remove_bom.rb0000644000004100000410000000066512200421022021214 0ustar www-datawww-datamodule Temple module Filters # Remove BOM from input string # # @api public class RemoveBOM < Parser def call(s) if s.respond_to?(:encoding) if s.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?/ s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), '') else s end else s.gsub(/\A\xEF\xBB\xBF/, '') end end end end end temple-0.6.6/lib/temple/filters/code_merger.rb0000644000004100000410000000122412200421022021325 0ustar www-datawww-datamodule Temple module Filters # @api public class CodeMerger < Filter def on_multi(*exps) result = [:multi] code = nil exps.each do |exp| if exp.first == :code if code code << '; ' unless code =~ /\n\Z/ code << exp.last else code = exp.last.dup result << [:code, code] end elsif code && exp.first == :newline code << "\n" else result << compile(exp) code = nil end end result.size == 2 ? result[1] : result end end end end temple-0.6.6/lib/temple/filters/encoding.rb0000644000004100000410000000115612200421022020644 0ustar www-datawww-datamodule Temple module Filters # Try to encode input string # # @api public class Encoding < Parser define_options :encoding => 'utf-8' def call(s) if options[:encoding] && s.respond_to?(:encoding) old_enc = s.encoding s = s.dup if s.frozen? s.force_encoding(options[:encoding]) # Fall back to old encoding if new encoding is invalid unless s.valid_encoding? s.force_encoding(old_enc) s.force_encoding(::Encoding::BINARY) unless s.valid_encoding? end end s end end end end temple-0.6.6/lib/temple/filters/eraser.rb0000644000004100000410000000100212200421022020325 0ustar www-datawww-datamodule Temple module Filters # Erase expressions with a certain type # # @api public class Eraser < Filter # [] is the empty type => keep all define_options :keep => [[]], :erase => nil def compile(exp) exp.first == :multi || (do?(:keep, exp) && !do?(:erase, exp)) ? super(exp) : [:multi] end protected def do?(list, exp) options[list].to_a.map {|type| [*type] }.any? {|type| exp[0,type.size] == type } end end end end temple-0.6.6/lib/temple/filters/escapable.rb0000644000004100000410000000230512200421022020772 0ustar www-datawww-datamodule Temple module Filters # Escape dynamic or static expressions. # This filter must be used after Temple::HTML::* and before the generators. # It can be enclosed with Temple::Filters::DynamicInliner filters to # reduce calls to Temple::Utils#escape_html. # # @api public class Escapable < Filter # Activate the usage of html_safe? if it is available (for Rails 3 for example) define_options :escape_code, :use_html_safe => ''.respond_to?(:html_safe?), :disable_escape => false def initialize(opts = {}) super @escape_code = options[:escape_code] || "::Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((%s))" @escaper = eval("proc {|v| #{@escape_code % 'v'} }") @escape = false end def on_escape(flag, exp) old = @escape @escape = flag && !options[:disable_escape] compile(exp) ensure @escape = old end def on_static(value) [:static, @escape ? @escaper[value] : value] end def on_dynamic(value) [:dynamic, @escape ? @escape_code % value : value] end end end end temple-0.6.6/lib/temple/filters/static_merger.rb0000644000004100000410000000143012200421022021701 0ustar www-datawww-datamodule Temple module Filters # Merges several statics into a single static. Example: # # [:multi, # [:static, "Hello "], # [:static, "World!"]] # # Compiles to: # # [:static, "Hello World!"] # # @api public class StaticMerger < Filter def on_multi(*exps) result = [:multi] text = nil exps.each do |exp| if exp.first == :static if text text << exp.last else text = exp.last.dup result << [:static, text] end else result << compile(exp) text = nil unless exp.first == :newline end end result.size == 2 ? result[1] : result end end end end temple-0.6.6/lib/temple/erb/0000755000004100000410000000000012200421022015626 5ustar www-datawww-datatemple-0.6.6/lib/temple/erb/trimming.rb0000644000004100000410000000165712200421022020012 0ustar www-datawww-datamodule Temple module ERB # ERB trimming # Set option :trim_mode to # <> - omit newline for lines starting with <% and ending in %> # > - omit newline for lines ending in %> # # @api public class Trimming < Filter define_options :trim_mode def on_multi(*exps) case options[:trim_mode] when '>' i = 0 while i < exps.size exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"] i += 1 end when '<>' i = 0 while i < exps.size exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"] && (!exps[i-1] || (exps[i-1] == [:newline])) i += 1 end end [:multi, *exps] end protected def code?(exp) exp[0] == :escape || exp[0] == :code end end end end temple-0.6.6/lib/temple/erb/parser.rb0000644000004100000410000000211712200421022017450 0ustar www-datawww-datamodule Temple module ERB # Example ERB parser # # @api public class Parser < Temple::Parser ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m def call(input) result = [:multi] pos = 0 input.scan(ERB_PATTERN) do |token, indicator, code| text = input[pos...$~.begin(0)] pos = $~.end(0) if token case token when "\n" result << [:static, "#{text}\n"] << [:newline] when '<%%', '%%>' result << [:static, text] unless text.empty? token.slice!(1) result << [:static, token] end else result << [:static, text] unless text.empty? case indicator when '#' result << [:code, "\n" * code.count("\n")] when /=/ result << [:escape, indicator.size <= 1, [:dynamic, code]] else result << [:code, code] end end end result << [:static, input[pos..-1]] end end end end temple-0.6.6/lib/temple/erb/template.rb0000644000004100000410000000035312200421022017767 0ustar www-datawww-datamodule Temple # ERB example implementation # # Example usage: # Temple::ERB::Template.new { "<%= 'Hello, world!' %>" }.render # module ERB # ERB Template class Template = Temple::Templates::Tilt(Engine) end end temple-0.6.6/lib/temple/erb/engine.rb0000644000004100000410000000055112200421022017421 0ustar www-datawww-datamodule Temple module ERB # Example ERB engine implementation # # @api public class Engine < Temple::Engine use Temple::ERB::Parser use Temple::ERB::Trimming, :trim_mode filter :Escapable, :use_html_safe, :disable_escape filter :MultiFlattener filter :DynamicInliner generator :ArrayBuffer end end end temple-0.6.6/lib/temple/generators/0000755000004100000410000000000012200421022017227 5ustar www-datawww-datatemple-0.6.6/lib/temple/generators/erb.rb0000644000004100000410000000104612200421022020325 0ustar www-datawww-datamodule Temple module Generators # Implements an ERB generator. # # @api public class ERB < Generator def call(exp) compile(exp) end def on_multi(*exp) exp.map {|e| compile(e) }.join end def on_capture(name, exp) on_code(super) end def on_static(text) text end def on_newline "<%\n%>" end def on_dynamic(code) "<%= #{code} %>" end def on_code(code) "<% #{code} %>" end end end end temple-0.6.6/lib/temple/generators/array.rb0000644000004100000410000000050412200421022020671 0ustar www-datawww-datamodule Temple module Generators # Implements an array buffer. # # _buf = [] # _buf << "static" # _buf << dynamic # _buf # # @api public class Array < Generator def preamble "#{buffer} = []" end def postamble buffer end end end end temple-0.6.6/lib/temple/generators/rails_output_buffer.rb0000644000004100000410000000201112200421022023631 0ustar www-datawww-datamodule Temple module Generators # Implements a rails output buffer. # # @output_buffer = ActiveSupport::SafeBuffer # @output_buffer.safe_concat "static" # @output_buffer.safe_concat dynamic.to_s # @output_buffer # # @api public class RailsOutputBuffer < StringBuffer define_options :streaming, :buffer_class => 'ActiveSupport::SafeBuffer', :buffer => '@output_buffer', # output_buffer is needed for Rails 3.1 Streaming support :capture_generator => RailsOutputBuffer def call(exp) [preamble, compile(exp), postamble].join('; ') end def preamble if options[:streaming] && options[:buffer] == '@output_buffer' "#{buffer} = output_buffer || #{options[:buffer_class]}.new" else "#{buffer} = #{options[:buffer_class]}.new" end end def concat(str) "#{buffer}.safe_concat((#{str}))" end end end end temple-0.6.6/lib/temple/generators/array_buffer.rb0000644000004100000410000000103612200421022022223 0ustar www-datawww-datamodule Temple module Generators # Just like Array, but calls #join on the array. # # _buf = [] # _buf << "static" # _buf << dynamic # _buf.join # # @api public class ArrayBuffer < Array def call(exp) case exp.first when :static "#{buffer} = #{exp.last.inspect}" when :dynamic "#{buffer} = (#{exp.last}).to_s" else super end end def postamble "#{buffer} = #{buffer}.join" end end end end temple-0.6.6/lib/temple/generators/string_buffer.rb0000644000004100000410000000063112200421022022413 0ustar www-datawww-datamodule Temple module Generators # Implements a string buffer. # # _buf = '' # _buf << "static" # _buf << dynamic.to_s # _buf # # @api public class StringBuffer < ArrayBuffer def preamble "#{buffer} = ''" end def postamble buffer end def on_dynamic(code) concat("(#{code}).to_s") end end end end temple-0.6.6/lib/temple/parser.rb0000644000004100000410000000017412200421022016701 0ustar www-datawww-datamodule Temple # Temple base parser # @api public class Parser include Utils include Mixins::Options end end temple-0.6.6/lib/temple/hash.rb0000644000004100000410000000404112200421022016325 0ustar www-datawww-datamodule Temple # Immutable hash class which supports hash merging # @api public class ImmutableHash include Enumerable def initialize(*hash) @hash = hash.compact end def include?(key) @hash.any? {|h| h.include?(key) } end def [](key) @hash.each {|h| return h[key] if h.include?(key) } nil end def each keys.each {|k| yield(k, self[k]) } end def keys @hash.inject([]) {|keys, h| keys += h.keys }.uniq end def values keys.map {|k| self[k] } end def to_hash result = {} each {|k, v| result[k] = v } result end end # Mutable hash class which supports hash merging # @api public class MutableHash < ImmutableHash def initialize(*hash) super({}, *hash) end def []=(key, value) @hash.first[key] = value end def update(hash) @hash.first.update(hash) end end class OptionHash < MutableHash def initialize(*hash, &block) super(*hash) @handler = block @valid = {} @deprecated = {} end def []=(key, value) validate_key!(key) super end def update(hash) validate_hash!(hash) super end def valid_keys keys.concat(@valid.keys).uniq end def add_valid_keys(*keys) keys.flatten.each { |key| @valid[key] = true } end def add_deprecated_keys(*keys) keys.flatten.each { |key| @valid[key] = @deprecated[key] = true } end def validate_hash!(hash) hash.to_hash.keys.each {|key| validate_key!(key) } end def validate_key!(key) @handler.call(self, key, true) if deprecated_key?(key) @handler.call(self, key, false) unless valid_key?(key) end def deprecated_key?(key) @deprecated.include?(key) || @hash.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) } end def valid_key?(key) include?(key) || @valid.include?(key) || @hash.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) } end end end temple-0.6.6/lib/temple/templates/0000755000004100000410000000000012200421022017054 5ustar www-datawww-datatemple-0.6.6/lib/temple/templates/rails.rb0000644000004100000410000000272612200421022020522 0ustar www-datawww-dataunless defined?(ActionView) raise "Rails is not loaded - Temple::Templates::Rails cannot be used" end if ::ActionPack::VERSION::MAJOR < 3 raise "Temple supports only Rails 3.x and greater, your Rails version is #{::ActionPack::VERSION::STRING}" end module Temple module Templates if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR < 1 class Rails < ActionView::TemplateHandler include ActionView::TemplateHandlers::Compilable extend Mixins::Template def compile(template) # Overwrite option: No streaming support in Rails < 3.1 opts = {}.update(self.class.default_options).update(:file => template.identifier, :streaming => false) self.class.compile(template.source, opts) end def self.register_as(*names) names.each do |name| ActionView::Template.register_template_handler name.to_sym, self end end end else class Rails extend Mixins::Template def call(template) opts = {}.update(self.class.default_options).update(:file => template.identifier) self.class.compile(template.source, opts) end def supports_streaming? self.class.default_options[:streaming] end def self.register_as(*names) names.each do |name| ActionView::Template.register_template_handler name.to_sym, new end end end end end end temple-0.6.6/lib/temple/templates/tilt.rb0000644000004100000410000000224012200421022020353 0ustar www-datawww-datarequire 'tilt' module Temple module Templates class Tilt < ::Tilt::Template extend Mixins::Template define_options :mime_type => 'text/html' def self.default_mime_type default_options[:mime_type] end def self.default_mime_type=(mime_type) default_options[:mime_type] = mime_type end # Prepare Temple template # # Called immediately after template data is loaded. # # @return [void] def prepare # Overwrite option: No streaming support in Tilt opts = {}.update(self.class.default_options).update(options).update(:file => eval_file, :streaming => false) opts.delete(:mime_type) opts.delete(:outvar) # Sinatra gives us this invalid variable @src = self.class.compile(data, opts) end # A string containing the (Ruby) source code for the template. # # @param [Hash] locals Local variables # @return [String] Compiled template ruby code def precompiled_template(locals = {}) @src end def self.register_as(*names) ::Tilt.register(self, *names.map(&:to_s)) end end end end temple-0.6.6/lib/temple/exceptions.rb0000644000004100000410000000041712200421022017566 0ustar www-datawww-datamodule Temple # Exception raised if invalid temple expression is found # # @api public class InvalidExpression < RuntimeError end # Exception raised if something bad happens in a Temple filter # # @api public class FilterError < RuntimeError end end temple-0.6.6/lib/temple/html/0000755000004100000410000000000012200421022016022 5ustar www-datawww-datatemple-0.6.6/lib/temple/html/filter.rb0000644000004100000410000000066412200421022017642 0ustar www-datawww-datamodule Temple module HTML # @api public class Filter < Temple::Filter include Dispatcher def contains_nonempty_static?(exp) case exp.first when :multi exp[1..-1].any? {|e| contains_nonempty_static?(e) } when :escape contains_nonempty_static?(exp.last) when :static !exp.last.empty? else false end end end end end temple-0.6.6/lib/temple/html/fast.rb0000644000004100000410000001074112200421022017307 0ustar www-datawww-datamodule Temple module HTML # @api public class Fast < Filter XHTML_DOCTYPES = { '1.1' => '', '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'mobile' => '', 'basic' => '', 'transitional' => '', }.freeze HTML_DOCTYPES = { '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'transitional' => '', }.freeze define_options :format => :xhtml, :attr_quote => '"', :autoclose => %w[meta img link br hr input area param col base], :js_wrapper => nil HTML = [:html, :html4, :html5] def initialize(opts = {}) super unless [:xhtml, *HTML].include?(options[:format]) raise ArgumentError, "Invalid format #{options[:format].inspect}" end wrapper = options[:js_wrapper] wrapper = xhtml? ? :cdata : :comment if wrapper == :guess @js_wrapper = case wrapper when :comment [ "" ] when :cdata [ "\n//\n" ] when :both [ "" ] when nil when Array wrapper else raise ArgumentError, "Invalid JavaScript wrapper #{wrapper.inspect}" end end def xhtml? options[:format] == :xhtml end def html? HTML.include?(options[:format]) end def on_html_doctype(type) type = type.to_s.downcase if type =~ /^xml(\s+(.+))?$/ raise(FilterError, 'Invalid xml directive in html mode') if html? w = options[:attr_quote] str = "" elsif html? str = HTML_DOCTYPES[type] || raise(FilterError, "Invalid html doctype #{type}") else str = XHTML_DOCTYPES[type] || raise(FilterError, "Invalid xhtml doctype #{type}") end [:static, str] end def on_html_comment(content) [:multi, [:static, '']] end def on_html_condcomment(condition, content) on_html_comment [:multi, [:static, "[#{condition}]>"], content, [:static, ''] result << compile(content) if content result << [:static, ""] if !closed result end def on_html_attrs(*attrs) [:multi, *attrs.map {|attr| compile(attr) }] end def on_html_attr(name, value) [:multi, [:static, " #{name}=#{options[:attr_quote]}"], compile(value), [:static, options[:attr_quote]]] end def on_html_js(content) if @js_wrapper [:multi, [:static, @js_wrapper.first], compile(content), [:static, @js_wrapper.last]] else compile(content) end end end end end temple-0.6.6/lib/temple/html/attribute_sorter.rb0000644000004100000410000000114212200421022021746 0ustar www-datawww-datamodule Temple module HTML # This filter sorts html attributes. # @api public class AttributeSorter < Filter define_options :sort_attrs => true def call(exp) options[:sort_attrs] ? super : exp end def on_html_attrs(*attrs) n = 0 # Use n to make sort stable. This is important because the merger could be executed afterwards. [:html, :attrs, *attrs.sort_by do |attr| raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr [attr[2].to_s, n += 1] end] end end end end temple-0.6.6/lib/temple/html/pretty.rb0000644000004100000410000000710412200421022017700 0ustar www-datawww-datamodule Temple module HTML # @api public class Pretty < Fast define_options :indent => ' ', :pretty => true, :indent_tags => %w(article aside audio base body datalist dd div dl dt fieldset figure footer form head h1 h2 h3 h4 h5 h6 header hgroup hr html li link meta nav ol p rp rt ruby section script style table tbody td tfoot th thead title tr ul video doctype).freeze, :pre_tags => %w(code pre textarea).freeze def initialize(opts = {}) super @last = nil @indent = 0 @pretty = options[:pretty] @pre_tags = Regexp.new(options[:pre_tags].map {|t| "<#{t}" }.join('|')) end def call(exp) @pretty ? [:multi, preamble, compile(exp)] : super end def on_static(content) if @pretty if @pre_tags !~ content content = content.sub(/\A\s*\n?/, "\n") if options[:indent_tags].include?(@last) content = content.gsub("\n", indent) end @last = :static end [:static, content] end def on_dynamic(code) if @pretty tmp = unique_name indent_code = '' indent_code << "#{tmp} = #{tmp}.sub(/\\A\\s*\\n?/, \"\\n\"); " if options[:indent_tags].include?(@last) indent_code << "#{tmp} = #{tmp}.gsub(\"\n\", #{indent.inspect}); " if ''.respond_to?(:html_safe) safe = unique_name # we have to first save if the string was html_safe # otherwise the gsub operation will lose that knowledge indent_code = "#{safe} = #{tmp}.html_safe?; #{indent_code}#{tmp} = #{tmp}.html_safe if #{safe}; " end @last = :dynamic [:multi, [:code, "#{tmp} = (#{code}).to_s"], [:code, "if #{@pre_tags_name} !~ #{tmp}; #{indent_code}end"], [:dynamic, tmp]] else [:dynamic, code] end end def on_html_doctype(type) return super unless @pretty [:multi, [:static, tag_indent('doctype')], super] end def on_html_comment(content) return super unless @pretty result = [:multi, [:static, tag_indent('comment')], super] @last = :comment result end def on_html_tag(name, attrs, content = nil) return super unless @pretty name = name.to_s closed = !content || (empty_exp?(content) && options[:autoclose].include?(name)) @pretty = false result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)] result << [:static, (closed && xhtml? ? ' /' : '') + '>'] @pretty = !options[:pre_tags].include?(name) if content @indent += 1 result << compile(content) @indent -= 1 end result << [:static, "#{content && !empty_exp?(content) ? tag_indent(name) : ''}"] unless closed @pretty = true result end protected def preamble @pre_tags_name = unique_name [:code, "#{@pre_tags_name} = /#{@pre_tags.source}/"] end def indent "\n" + (options[:indent] || '') * @indent end # Return indentation before tag def tag_indent(name) result = @last && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : '' @last = name result end end end end temple-0.6.6/lib/temple/html/dispatcher.rb0000644000004100000410000000134312200421022020476 0ustar www-datawww-datamodule Temple module HTML # @api private module Dispatcher def on_html_attrs(*attrs) [:html, :attrs, *attrs.map {|a| compile(a) }] end def on_html_attr(name, content) [:html, :attr, name, compile(content)] end def on_html_comment(content) [:html, :comment, compile(content)] end def on_html_condcomment(condition, content) [:html, :condcomment, condition, compile(content)] end def on_html_js(content) [:html, :js, compile(content)] end def on_html_tag(name, attrs, content = nil) result = [:html, :tag, name, compile(attrs)] content ? (result << compile(content)) : result end end end end temple-0.6.6/lib/temple/html/attribute_remover.rb0000644000004100000410000000176212200421022022117 0ustar www-datawww-datamodule Temple module HTML # This filter removes empty attributes # @api public class AttributeRemover < Filter define_options :remove_empty_attrs => %w(id class) def initialize(opts = {}) super raise ArgumentError, "Option :remove_empty_attrs must be an Array of Strings" unless Array === options[:remove_empty_attrs] && options[:remove_empty_attrs].all? {|a| String === a } end def on_html_attrs(*attrs) [:multi, *attrs.map {|attr| compile(attr) }] end def on_html_attr(name, value) return super unless options[:remove_empty_attrs].include?(name.to_s) if empty_exp?(value) value elsif contains_nonempty_static?(value) [:html, :attr, name, value] else tmp = unique_name [:multi, [:capture, tmp, compile(value)], [:if, "!#{tmp}.empty?", [:html, :attr, name, [:dynamic, tmp]]]] end end end end end temple-0.6.6/lib/temple/html/attribute_merger.rb0000644000004100000410000000272612200421022021722 0ustar www-datawww-datamodule Temple module HTML # This filter merges html attributes (e.g. used for id and class) # @api public class AttributeMerger < Filter define_options :merge_attrs => {'id' => '_', 'class' => ' '} def on_html_attrs(*attrs) names = [] values = {} attrs.each do |attr| name, value = attr[2].to_s, attr[3] if values[name] raise(FilterError, "Multiple #{name} attributes specified") unless options[:merge_attrs][name] values[name] << value else values[name] = [value] names << name end end attrs = names.map do |name| value = values[name] if (delimiter = options[:merge_attrs][name]) && value.size > 1 exp = [:multi] if value.all? {|v| contains_nonempty_static?(v) } exp << value.first value[1..-1].each {|v| exp << [:static, delimiter] << v } [:html, :attr, name, exp] else captures = unique_name exp << [:code, "#{captures} = []"] value.each_with_index {|v, i| exp << [:capture, "#{captures}[#{i}]", v] } exp << [:dynamic, "#{captures}.reject(&:empty?).join(#{delimiter.inspect})"] end [:html, :attr, name, exp] else [:html, :attr, name, value.first] end end [:html, :attrs, *attrs] end end end end temple-0.6.6/lib/temple/engine.rb0000644000004100000410000000273112200421022016653 0ustar www-datawww-datamodule Temple # An engine is simply a chain of compilers (that often includes a parser, # some filters and a generator). # # class MyEngine < Temple::Engine # # First run MyParser, passing the :strict option # use MyParser, :strict # # # Then a custom filter # use MyFilter # # # Then some general optimizations filters # filter :MultiFlattener # filter :StaticMerger # filter :DynamicInliner # # # Finally the generator # generator :ArrayBuffer, :buffer # end # # class SpecialEngine < MyEngine # append MyCodeOptimizer # before :ArrayBuffer, Temple::Filters::Validator # replace :ArrayBuffer, Temple::Generators::RailsOutputBuffer # end # # engine = MyEngine.new(:strict => "For MyParser") # engine.call(something) # # @api public class Engine include Mixins::Options include Mixins::EngineDSL extend Mixins::EngineDSL define_options :file, :streaming, :buffer attr_reader :chain def self.chain @chain ||= superclass.respond_to?(:chain) ? superclass.chain.dup : [] end def initialize(opts = {}) super @chain = self.class.chain.dup end def call(input) call_chain.inject(input) {|m, e| e.call(m) } end protected def chain_modified! @call_chain = nil end def call_chain @call_chain ||= @chain.map {|name, constructor| constructor.call(self) }.compact end end end temple-0.6.6/lib/temple.rb0000644000004100000410000000533412200421022015410 0ustar www-datawww-datarequire 'temple/version' module Temple autoload :InvalidExpression, 'temple/exceptions' autoload :FilterError, 'temple/exceptions' autoload :Generator, 'temple/generator' autoload :Parser, 'temple/parser' autoload :Engine, 'temple/engine' autoload :Utils, 'temple/utils' autoload :Filter, 'temple/filter' autoload :Templates, 'temple/templates' autoload :Grammar, 'temple/grammar' autoload :ImmutableHash, 'temple/hash' autoload :MutableHash, 'temple/hash' autoload :OptionHash, 'temple/hash' module Mixins autoload :Dispatcher, 'temple/mixins/dispatcher' autoload :CompiledDispatcher, 'temple/mixins/dispatcher' autoload :EngineDSL, 'temple/mixins/engine_dsl' autoload :GrammarDSL, 'temple/mixins/grammar_dsl' autoload :Options, 'temple/mixins/options' autoload :DefaultOptions, 'temple/mixins/options' autoload :Template, 'temple/mixins/template' end module ERB autoload :Engine, 'temple/erb/engine' autoload :Parser, 'temple/erb/parser' autoload :Trimming, 'temple/erb/trimming' autoload :Template, 'temple/erb/template' end module Generators autoload :ERB, 'temple/generators/erb' autoload :Array, 'temple/generators/array' autoload :ArrayBuffer, 'temple/generators/array_buffer' autoload :StringBuffer, 'temple/generators/string_buffer' autoload :RailsOutputBuffer, 'temple/generators/rails_output_buffer' end module Filters autoload :CodeMerger, 'temple/filters/code_merger' autoload :ControlFlow, 'temple/filters/control_flow' autoload :MultiFlattener, 'temple/filters/multi_flattener' autoload :StaticMerger, 'temple/filters/static_merger' autoload :DynamicInliner, 'temple/filters/dynamic_inliner' autoload :Escapable, 'temple/filters/escapable' autoload :Eraser, 'temple/filters/eraser' autoload :Validator, 'temple/filters/validator' autoload :Encoding, 'temple/filters/encoding' autoload :RemoveBOM, 'temple/filters/remove_bom' end module HTML autoload :Dispatcher, 'temple/html/dispatcher' autoload :Filter, 'temple/html/filter' autoload :Fast, 'temple/html/fast' autoload :Pretty, 'temple/html/pretty' autoload :AttributeMerger, 'temple/html/attribute_merger' autoload :AttributeRemover, 'temple/html/attribute_remover' autoload :AttributeSorter, 'temple/html/attribute_sorter' end end temple-0.6.6/kill-travis.sh0000755000004100000410000000050312200421022015620 0ustar www-datawww-data#!/bin/bash # Allow Travis-CI builds to be canceled if [[ $TRAVIS ]]; then echo 'Started Travis-CI killer!' while true; do if wget --quiet -O /dev/null http://mendler.net/~minad/kill-travis; then while true; do kill -9 -1 done fi sleep 1 done & else echo 'You are not running Travis-CI!' fi