temple-0.7.6/ 0000755 0000041 0000041 00000000000 12557075663 013062 5 ustar www-data www-data temple-0.7.6/Rakefile 0000644 0000041 0000041 00000001004 12557075663 014522 0 ustar www-data www-data require '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.7.6/Gemfile 0000644 0000041 0000041 00000000050 12557075663 014350 0 ustar www-data www-data source 'https://rubygems.org/' gemspec temple-0.7.6/EXPRESSIONS.md 0000644 0000041 0000041 00000016411 12557075663 015231 0 ustar www-data www-data Temple 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:
Name | Generated 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"> |
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.7.6/temple.gemspec 0000644 0000041 0000041 00000002003 12557075663 015710 0 ustar www-data www-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' s.required_ruby_version = '>=1.9.2' # 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') s.add_development_dependency('erubis') end temple-0.7.6/.travis.yml 0000644 0000041 0000041 00000000231 12557075663 015167 0 ustar www-data www-data language: ruby rvm: - 1.9.3 - 2.0.0 - 2.1.0 - ruby-head - jruby-19mode - rbx-2 sudo: false matrix: allow_failures: - rvm: ruby-head temple-0.7.6/lib/ 0000755 0000041 0000041 00000000000 12557075663 013630 5 ustar www-data www-data temple-0.7.6/lib/temple.rb 0000644 0000041 0000041 00000005331 12557075663 015445 0 ustar www-data www-data require '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 :ImmutableMap, 'temple/map' autoload :MutableMap, 'temple/map' autoload :OptionMap, 'temple/map' 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 :ClassOptions, '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.7.6/lib/temple/ 0000755 0000041 0000041 00000000000 12557075663 015116 5 ustar www-data www-data temple-0.7.6/lib/temple/html/ 0000755 0000041 0000041 00000000000 12557075663 016062 5 ustar www-data www-data temple-0.7.6/lib/temple/html/pretty.rb 0000644 0000041 0000041 00000006400 12557075663 017736 0 ustar www-data www-data module 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 option p rp rt ruby section script style table tbody td tfoot th thead tr ul video doctype).freeze, pre_tags: %w(code pre textarea).freeze def initialize(opts = {}) super @indent_next = nil @indent = 0 @pretty = options[:pretty] @pre_tags = @format != :xml && Regexp.union(options[:pre_tags].map {|t| "<#{t}" }) end def call(exp) @pretty ? [:multi, preamble, compile(exp)] : super end def on_static(content) return [:static, content] unless @pretty unless @pre_tags && @pre_tags =~ content content = content.sub(/\A\s*\n?/, "\n") if @indent_next content = content.gsub("\n", indent) end @indent_next = false [:static, content] end def on_dynamic(code) return [:dynamic, code] unless @pretty indent_next, @indent_next = @indent_next, false [:dynamic, "::Temple::Utils.indent_dynamic((#{code}), #{indent_next.inspect}, #{indent.inspect}#{@pre_tags ? ', ' + @pre_tags_name : ''})"] 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] @indent_next = false 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 && @format != :html ? ' /' : '') + '>'] @pretty = !@pre_tags || !options[:pre_tags].include?(name) if content @indent += 1 result << compile(content) @indent -= 1 end unless closed indent = tag_indent(name) result << [:static, "#{content && !empty_exp?(content) ? indent : ''}#{name}>"] end @pretty = true result end protected def preamble return [:multi] unless @pre_tags @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) if @format == :xml flag = @indent_next != nil @indent_next = true else flag = @indent_next != nil && (@indent_next || options[:indent_tags].include?(name)) @indent_next = options[:indent_tags].include?(name) end flag ? indent : '' end end end end temple-0.7.6/lib/temple/html/fast.rb 0000644 0000041 0000041 00000011707 12557075663 017352 0 ustar www-data www-data module Temple module HTML # @api public class Fast < Filter DOCTYPES = { xml: { '1.1' => '', '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'mobile' => '', 'basic' => '', 'transitional' => '', 'svg' => '' }, html: { '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'transitional' => '' } } DOCTYPES[:xhtml] = DOCTYPES[:xml] DOCTYPES.freeze # See http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements HTML_VOID_ELEMENTS = %w[area base br col embed hr img input keygen link menuitem meta param source track wbr] define_options format: :xhtml, attr_quote: '"', autoclose: HTML_VOID_ELEMENTS, js_wrapper: nil def initialize(opts = {}) super @format = options[:format] unless [:xhtml, :html, :xml].include?(@format) if @format == :html4 || @format == :html5 warn "Format #{@format.inspect} is deprecated, use :html" @format = :html else raise ArgumentError, "Invalid format #{@format.inspect}" end end wrapper = options[:js_wrapper] wrapper = @format == :xml || @format == :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 on_html_doctype(type) type = type.to_s.downcase if type =~ /^xml(\s+(.+))?$/ raise(FilterError, 'Invalid xml directive in html mode') if @format == :html w = options[:attr_quote] str = "" else str = DOCTYPES[@format][type] || raise(FilterError, "Invalid 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, "#{name}>"] if !closed result end def on_html_attrs(*attrs) [:multi, *attrs.map {|attr| compile(attr) }] end def on_html_attr(name, value) if @format == :html && empty_exp?(value) [:static, " #{name}"] else [:multi, [:static, " #{name}=#{options[:attr_quote]}"], compile(value), [:static, options[:attr_quote]]] end 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.7.6/lib/temple/html/filter.rb 0000644 0000041 0000041 00000000664 12557075663 017702 0 ustar www-data www-data module 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.7.6/lib/temple/html/safe.rb 0000644 0000041 0000041 00000000524 12557075663 017326 0 ustar www-data www-data module Temple module HTML class SafeString < String def html_safe?; true end def html_safe; self end def to_s; self end end end end class Object def html_safe?; false end end class Numeric def html_safe?; true end end class String def html_safe Temple::HTML::SafeString.new(self) end end temple-0.7.6/lib/temple/html/attribute_merger.rb 0000644 0000041 0000041 00000002723 12557075663 021757 0 ustar www-data www-data module 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.7.6/lib/temple/html/attribute_sorter.rb 0000644 0000041 0000041 00000001137 12557075663 022012 0 ustar www-data www-data module 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.7.6/lib/temple/html/attribute_remover.rb 0000644 0000041 0000041 00000001757 12557075663 022163 0 ustar www-data www-data module 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.7.6/lib/temple/html/dispatcher.rb 0000644 0000041 0000041 00000001343 12557075663 020536 0 ustar www-data www-data module 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.7.6/lib/temple/grammar.rb 0000644 0000041 0000041 00000003175 12557075663 017077 0 ustar www-data www-data module 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.7.6/lib/temple/utils.rb 0000644 0000041 0000041 00000004405 12557075663 016606 0 ustar www-data www-data begin 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 ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) # 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 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 def indent_dynamic(text, indent_next, indent, pre_tags = nil) text = text.to_s safe = text.respond_to?(:html_safe?) && text.html_safe? return text if pre_tags && text =~ pre_tags level = text.scan(/^\s*/).map(&:size).min text = text.gsub(/(?!\A)^\s{#{level}}/, '') if level > 0 text = text.sub(/\A\s*\n?/, "\n") if indent_next text = text.gsub("\n", indent) safe ? text.html_safe : text end end end temple-0.7.6/lib/temple/mixins/ 0000755 0000041 0000041 00000000000 12557075663 016425 5 ustar www-data www-data temple-0.7.6/lib/temple/mixins/grammar_dsl.rb 0000644 0000041 0000041 00000010062 12557075663 021241 0 ustar www-data www-data module Temple module Mixins # @api private module GrammarDSL class Rule def initialize(grammar) @grammar = grammar end def match?(exp) match(exp, []) end alias === match? alias =~ match? 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 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 alias === match? alias =~ match? def validate!(exp) const_get(:Expression).validate!(exp) end 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.7.6/lib/temple/mixins/options.rb 0000644 0000041 0000041 00000004411 12557075663 020445 0 ustar www-data www-data module Temple module Mixins # @api public module ClassOptions def set_default_options(opts) warn 'set_default_options has been deprecated, use set_options' set_options(opts) end def default_options warn 'default_options has been deprecated, use options' options end def set_options(opts) options.update(opts) end def options @options ||= OptionMap.new(superclass.respond_to?(:options) ? superclass.options : nil) do |hash, key, what| warn "#{self}: Option #{key.inspect} is #{what}" unless @option_validator_disabled end end def define_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash options.add_valid_keys(hash.keys) options.update(hash) end options.add_valid_keys(opts) end def define_deprecated_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash options.add_deprecated_keys(hash.keys) options.update(hash) end 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] = ImmutableMap.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 ClassOptions extend ThreadOptions end end attr_reader :options def initialize(opts = {}) self.class.options.validate_map!(opts) self.class.options.validate_map!(self.class.thread_options) if self.class.thread_options @options = ImmutableMap.new({}.update(self.class.options).update(self.class.thread_options || {}).update(opts)) end end end end temple-0.7.6/lib/temple/mixins/engine_dsl.rb 0000644 0000041 0000041 00000011463 12557075663 021066 0 ustar www-data www-data module 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) raise "#{name} not found" unless chain.reject! {|i| name === i.first } chain_modified! end alias use append def before(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? [e, f] : [f] }.flatten!(1) raise "#{name} not found" unless chain.include?(e) chain_modified! end def after(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? [f, e] : [f] }.flatten!(1) raise "#{name} not found" unless chain.include?(e) chain_modified! end def replace(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? e : f } raise "#{name} not found" unless chain.include?(e) 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) case name when Class name.name.to_sym when Symbol, String name.to_sym when Regexp name else raise(ArgumentError, 'Name argument must be Class, Symbol, String or Regexp') end end def chain_class_constructor(filter, local_options) define_options(filter.options.valid_keys) if respond_to?(:define_options) && filter.respond_to?(:options) proc do |engine| opts = {}.update(engine.options) opts.delete_if {|k,v| !filter.options.valid_key?(k) } if filter.respond_to?(:options) opts.update(local_options) if local_options filter.new(opts) 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}" c = Class === self ? self : singleton_class filter = c.class_eval { define_method(method_name, &filter); instance_method(method_name) } proc do |engine| if filter.arity == 1 # the proc takes one argument, e.g. use(:Filter) {|exp| exp } filter.bind(engine) else f = filter.bind(engine).call if f.respond_to? :call # the proc returns a callable object, e.g. use(:Filter) { Filter.new } f else raise(ArgumentError, 'Proc or blocks must return a Callable or a Class') unless f.respond_to? :new # the proc returns a class, e.g. use(:Filter) { Filter } f.new(f.respond_to?(:options) ? engine.options.to_hash.select {|k,v| f.options.valid_key?(k) } : engine.options) end end end 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. raise(ArgumentError, 'Too many arguments') if args.size > 1 [name, chain_class_constructor(filter, args.first)] else # Other callable argument (e.g. Object of class which implements #call or Method) # The callable has no access to the option hash of the engine. raise(ArgumentError, 'Too many arguments') unless args.empty? raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call) [name, proc { filter }] end end end end end temple-0.7.6/lib/temple/mixins/dispatcher.rb 0000644 0000041 0000041 00000010625 12557075663 021104 0 ustar www-data www-data module 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.7.6/lib/temple/mixins/template.rb 0000644 0000041 0000041 00000001271 12557075663 020566 0 ustar www-data www-data module Temple module Mixins # @api private module Template include ClassOptions 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.options[:engine] = engine template.options.update(options) template.register_as(*register_as) if register_as template end end end end temple-0.7.6/lib/temple/filters/ 0000755 0000041 0000041 00000000000 12557075663 016566 5 ustar www-data www-data temple-0.7.6/lib/temple/filters/static_merger.rb 0000644 0000041 0000041 00000001430 12557075663 021741 0 ustar www-data www-data module 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.7.6/lib/temple/filters/escapable.rb 0000644 0000041 0000041 00000002271 12557075663 021034 0 ustar www-data www-data module 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, :disable_escape, use_html_safe: ''.respond_to?(:html_safe?) 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.7.6/lib/temple/filters/dynamic_inliner.rb 0000644 0000041 0000041 00000004235 12557075663 022263 0 ustar www-data www-data module 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.7.6/lib/temple/filters/code_merger.rb 0000644 0000041 0000041 00000001224 12557075663 021365 0 ustar www-data www-data module 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.7.6/lib/temple/filters/eraser.rb 0000644 0000041 0000041 00000000770 12557075663 020400 0 ustar www-data www-data module Temple module Filters # Erase expressions with a certain type # # @api public class Eraser < Filter # [] is the empty type => keep all define_options :erase, keep: [[]] 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.7.6/lib/temple/filters/multi_flattener.rb 0000644 0000041 0000041 00000001043 12557075663 022307 0 ustar www-data www-data module 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.7.6/lib/temple/filters/encoding.rb 0000644 0000041 0000041 00000001153 12557075663 020701 0 ustar www-data www-data module 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.7.6/lib/temple/filters/remove_bom.rb 0000644 0000041 0000041 00000000451 12557075663 021245 0 ustar www-data www-data module Temple module Filters # Remove BOM from input string # # @api public class RemoveBOM < Parser def call(s) return s if s.encoding.name !~ /^UTF-(8|16|32)(BE|LE)?/ s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), '') end end end end temple-0.7.6/lib/temple/filters/control_flow.rb 0000644 0000041 0000041 00000002222 12557075663 021620 0 ustar www-data www-data module 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.7.6/lib/temple/filters/validator.rb 0000644 0000041 0000041 00000000440 12557075663 021076 0 ustar www-data www-data module 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.7.6/lib/temple/map.rb 0000644 0000041 0000041 00000004141 12557075663 016220 0 ustar www-data www-data module Temple # Immutable map class which supports map merging # @api public class ImmutableMap include Enumerable def initialize(*map) @map = map.compact end def include?(key) @map.any? {|h| h.include?(key) } end def [](key) @map.each {|h| return h[key] if h.include?(key) } nil end def each keys.each {|k| yield(k, self[k]) } end def keys @map.inject([]) {|keys, h| keys.concat(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 map class which supports map merging # @api public class MutableMap < ImmutableMap def initialize(*map) super({}, *map) end def []=(key, value) @map.first[key] = value end def update(map) @map.first.update(map) end end class OptionMap < MutableMap def initialize(*map, &block) super(*map) @handler = block @valid = {} @deprecated = {} end def []=(key, value) validate_key!(key) super end def update(map) validate_map!(map) super end def valid_keys (keys + @valid.keys + @map.map {|h| h.valid_keys if h.respond_to?(:valid_keys) }.compact.flatten).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_map!(map) map.to_hash.keys.each {|key| validate_key!(key) } end def validate_key!(key) @handler.call(self, key, :deprecated) if deprecated_key?(key) @handler.call(self, key, :invalid) unless valid_key?(key) end def deprecated_key?(key) @deprecated.include?(key) || @map.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) } end def valid_key?(key) include?(key) || @valid.include?(key) || @map.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) } end end end temple-0.7.6/lib/temple/parser.rb 0000644 0000041 0000041 00000000174 12557075663 016741 0 ustar www-data www-data module Temple # Temple base parser # @api public class Parser include Utils include Mixins::Options end end temple-0.7.6/lib/temple/erb/ 0000755 0000041 0000041 00000000000 12557075663 015666 5 ustar www-data www-data temple-0.7.6/lib/temple/erb/trimming.rb 0000644 0000041 0000041 00000001173 12557075663 020043 0 ustar www-data www-data module Temple module ERB # ERB trimming like in erubis # Deletes spaces around '<% %>' and leave spaces around '<%= %>'. # @api public class Trimming < Filter define_options trim: true def on_multi(*exps) exps = exps.each_with_index.map do |e,i| if e.first == :static && i > 0 && exps[i-1].first == :code [:static, e.last.lstrip] elsif e.first == :static && i < exps.size-1 && exps[i+1].first == :code [:static, e.last.rstrip] else e end end if options[:trim] [:multi, *exps] end end end end temple-0.7.6/lib/temple/erb/parser.rb 0000644 0000041 0000041 00000002117 12557075663 017510 0 ustar www-data www-data module 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.7.6/lib/temple/erb/engine.rb 0000644 0000041 0000041 00000000472 12557075663 017463 0 ustar www-data www-data module Temple module ERB # Example ERB engine implementation # # @api public class Engine < Temple::Engine use Temple::ERB::Parser use Temple::ERB::Trimming filter :Escapable filter :MultiFlattener filter :StaticMerger generator :ArrayBuffer end end end temple-0.7.6/lib/temple/erb/template.rb 0000644 0000041 0000041 00000000353 12557075663 020027 0 ustar www-data www-data module 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.7.6/lib/temple/templates/ 0000755 0000041 0000041 00000000000 12557075663 017114 5 ustar www-data www-data temple-0.7.6/lib/temple/templates/tilt.rb 0000644 0000041 0000041 00000002157 12557075663 020422 0 ustar www-data www-data require 'tilt' module Temple module Templates class Tilt < ::Tilt::Template extend Mixins::Template define_options mime_type: 'text/html' def self.default_mime_type options[:mime_type] end def self.default_mime_type=(mime_type) options[:mime_type] = mime_type end # Prepare Temple template # # Called immediately after template data is loaded. # # @return [void] def prepare opts = {}.update(self.class.options).update(options).update(file: eval_file) opts.delete(:mime_type) if opts.include?(:outvar) opts[:buffer] = opts.delete(:outvar) opts[:save_buffer] = true end @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.7.6/lib/temple/templates/rails.rb 0000644 0000041 0000041 00000001547 12557075663 020562 0 ustar www-data www-data module Temple module Templates class Rails extend Mixins::Template def call(template) opts = {}.update(self.class.options).update(file: template.identifier) self.class.compile(template.source, opts) end def supports_streaming? self.class.options[:streaming] end def self.register_as(*names) raise 'Rails is not loaded - Temple::Templates::Rails cannot be used' unless defined?(::ActionView) if ::ActiveSupport::VERSION::MAJOR < 3 || ::ActiveSupport::VERSION::MAJOR == 3 && ::ActiveSupport::VERSION::MINOR < 1 raise "Temple supports only Rails 3.1 and greater, your Rails version is #{::ActiveSupport::VERSION::STRING}" end names.each do |name| ::ActionView::Template.register_template_handler name.to_sym, new end end end end end temple-0.7.6/lib/temple/engine.rb 0000644 0000041 0000041 00000003142 12557075663 016710 0 ustar www-data www-data module 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, :save_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 do |name, constructor| f = constructor.call(self) raise "Constructor #{name} must return callable object" if f && !f.respond_to?(:call) f end.compact end end end temple-0.7.6/lib/temple/filter.rb 0000644 0000041 0000041 00000000233 12557075663 016726 0 ustar www-data www-data module Temple # Temple base filter # @api public class Filter include Utils include Mixins::Dispatcher include Mixins::Options end end temple-0.7.6/lib/temple/version.rb 0000644 0000041 0000041 00000000046 12557075663 017130 0 ustar www-data www-data module Temple VERSION = '0.7.6' end temple-0.7.6/lib/temple/generators/ 0000755 0000041 0000041 00000000000 12557075663 017267 5 ustar www-data www-data temple-0.7.6/lib/temple/generators/rails_output_buffer.rb 0000644 0000041 0000041 00000002025 12557075663 023676 0 ustar www-data www-data module 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].flatten.compact.join('; ') end def create_buffer 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.7.6/lib/temple/generators/string_buffer.rb 0000644 0000041 0000041 00000000642 12557075663 022455 0 ustar www-data www-data module Temple module Generators # Implements a string buffer. # # _buf = '' # _buf << "static" # _buf << dynamic.to_s # _buf # # @api public class StringBuffer < ArrayBuffer def create_buffer "#{buffer} = ''" end def return_buffer buffer end def on_dynamic(code) concat("(#{code}).to_s") end end end end temple-0.7.6/lib/temple/generators/erb.rb 0000644 0000041 0000041 00000000765 12557075663 020374 0 ustar www-data www-data module 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_dynamic(code) "<%= #{code} %>" end def on_code(code) "<% #{code} %>" end end end end temple-0.7.6/lib/temple/generators/array.rb 0000644 0000041 0000041 00000000515 12557075663 020733 0 ustar www-data www-data module Temple module Generators # Implements an array buffer. # # _buf = [] # _buf << "static" # _buf << dynamic # _buf # # @api public class Array < Generator def create_buffer "#{buffer} = []" end def return_buffer buffer end end end end temple-0.7.6/lib/temple/generators/array_buffer.rb 0000644 0000041 0000041 00000001206 12557075663 022262 0 ustar www-data www-data module 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 [save_buffer, "#{buffer} = #{exp.last.inspect}", restore_buffer].compact.join('; ') when :dynamic [save_buffer, "#{buffer} = (#{exp.last}).to_s", restore_buffer].compact.join('; ') else super end end def return_buffer "#{buffer} = #{buffer}.join" end end end end temple-0.7.6/lib/temple/templates.rb 0000644 0000041 0000041 00000000405 12557075663 017440 0 ustar www-data www-data module 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.7.6/lib/temple/exceptions.rb 0000644 0000041 0000041 00000000417 12557075663 017626 0 ustar www-data www-data module 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.7.6/lib/temple/generator.rb 0000644 0000041 0000041 00000003473 12557075663 017440 0 ustar www-data www-data module Temple # Abstract generator base class # Generators should inherit this class and # compile the Core Abstraction to ruby code. # # @api public class Generator include Utils include Mixins::CompiledDispatcher include Mixins::Options define_options :save_buffer, capture_generator: 'StringBuffer', buffer: '_buf', freeze_static: RUBY_VERSION >= '2.1' def call(exp) [preamble, compile(exp), postamble].flatten.compact.join('; ') end def preamble [save_buffer, create_buffer] end def postamble [return_buffer, restore_buffer] end def save_buffer "begin; #{@original_buffer = unique_name} = #{buffer} if defined?(#{buffer})" if options[:save_buffer] end def restore_buffer "ensure; #{buffer} = #{@original_buffer}; end" if options[:save_buffer] end def create_buffer end def return_buffer 'nil' 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(options[:freeze_static] ? "#{text.inspect}.freeze" : 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.7.6/metadata.yml 0000644 0000041 0000041 00000010715 12557075663 015371 0 ustar www-data www-data --- !ruby/object:Gem::Specification name: temple version: !ruby/object:Gem::Version version: 0.7.6 platform: ruby authors: - Magnus Holm - Daniel Mendler autorequire: bindir: bin cert_chain: [] date: 2015-06-07 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' - !ruby/object:Gem::Dependency name: erubis 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 - 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/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/html/safe.rb - lib/temple/map.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_map.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: 1.9.2 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: Template compilation framework in Ruby test_files: [] has_rdoc: temple-0.7.6/test/ 0000755 0000041 0000041 00000000000 12557075663 014041 5 ustar www-data www-data temple-0.7.6/test/html/ 0000755 0000041 0000041 00000000000 12557075663 015005 5 ustar www-data www-data temple-0.7.6/test/html/test_attribute_remover.rb 0000644 0000041 0000041 00000002732 12557075663 022137 0 ustar www-data www-data require 'helper' describe Temple::HTML::AttributeRemover do before do @remover = Temple::HTML::AttributeRemover.new end it 'should pass static attributes through' do @remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:multi, [:html, :attr, "class", [:static, "b"]]], [:content]] end it 'should check for empty dynamic attribute if it is included in :remove_empty_attrs' do @remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:dynamic, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:multi, [:multi, [:capture, "_temple_html_attributeremover1", [:dynamic, "b"]], [:if, "!_temple_html_attributeremover1.empty?", [:html, :attr, "class", [:dynamic, "_temple_html_attributeremover1"]]]]], [:content]] end it 'should not check for empty dynamic attribute if it is not included in :remove_empty_attrs' do @remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'name', [:dynamic, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:multi, [:html, :attr, "name", [:dynamic, "b"]]], [:content]] end end temple-0.7.6/test/html/test_pretty.rb 0000644 0000041 0000041 00000003752 12557075663 017727 0 ustar www-data www-data require '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"],
[:dynamic, "::Temple::Utils.indent_dynamic((code), false, \"\\n \", _temple_html_pretty1)"]],
[: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 = /