temple-0.8.0/ 0000755 0000041 0000041 00000000000 13053463700 013036 5 ustar www-data www-data temple-0.8.0/Rakefile 0000644 0000041 0000041 00000001004 13053463700 014476 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.8.0/Gemfile 0000644 0000041 0000041 00000000121 13053463700 014323 0 ustar www-data www-data source 'https://rubygems.org/' gemspec gem 'escape_utils' if ENV['ESCAPE_UTILS'] temple-0.8.0/EXPRESSIONS.md 0000644 0000041 0000041 00000016416 13053463700 015212 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.8.0/temple.gemspec 0000644 0000041 0000041 00000002003 13053463700 015664 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.8.0/.travis.yml 0000644 0000041 0000041 00000000544 13053463700 015152 0 ustar www-data www-data language: ruby dist: trusty cache: bundler rvm: - 1.9.3 - 2.0.0 - 2.1.10 - 2.2.6 - 2.3.3 - ruby-head - jruby-19mode - rbx-3 sudo: false env: - ESCAPE_UTILS=1 - "" matrix: allow_failures: - rvm: ruby-head - rvm: rbx-3 exclude: - rvm: jruby-19mode env: ESCAPE_UTILS=1 - rvm: rbx-3 env: ESCAPE_UTILS=1 temple-0.8.0/lib/ 0000755 0000041 0000041 00000000000 13053463700 013604 5 ustar www-data www-data temple-0.8.0/lib/temple.rb 0000644 0000041 0000041 00000005632 13053463700 015425 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' autoload :StaticAnalyzer, 'temple/static_analyzer' 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 :StaticAnalyzer, 'temple/filters/static_analyzer' autoload :StaticMerger, 'temple/filters/static_merger' autoload :StringSplitter, 'temple/filters/string_splitter' 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.8.0/lib/temple/ 0000755 0000041 0000041 00000000000 13053463700 015072 5 ustar www-data www-data temple-0.8.0/lib/temple/html/ 0000755 0000041 0000041 00000000000 13053463700 016036 5 ustar www-data www-data temple-0.8.0/lib/temple/html/pretty.rb 0000644 0000041 0000041 00000006416 13053463700 017721 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".freeze) if @indent_next content = content.gsub("\n".freeze, 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.8.0/lib/temple/html/fast.rb 0000644 0000041 0000041 00000011707 13053463700 017326 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.8.0/lib/temple/html/filter.rb 0000644 0000041 0000041 00000000664 13053463700 017656 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.8.0/lib/temple/html/safe.rb 0000644 0000041 0000041 00000000524 13053463700 017302 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.8.0/lib/temple/html/attribute_merger.rb 0000644 0000041 0000041 00000002516 13053463700 021733 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) values = {} attrs.each do |_, _, name, value| name = name.to_s if values[name] raise(FilterError, "Multiple #{name} attributes specified") unless options[:merge_attrs][name] values[name] << value else values[name] = [value] end end attrs = values.map do |name, value| 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 } 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 else exp = value.first end [:html, :attr, name, exp] end [:html, :attrs, *attrs] end end end end temple-0.8.0/lib/temple/html/attribute_sorter.rb 0000644 0000041 0000041 00000001137 13053463700 021766 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.8.0/lib/temple/html/attribute_remover.rb 0000644 0000041 0000041 00000001757 13053463700 022137 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.8.0/lib/temple/html/dispatcher.rb 0000644 0000041 0000041 00000001343 13053463700 020512 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.8.0/lib/temple/grammar.rb 0000644 0000041 0000041 00000003175 13053463700 017053 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.8.0/lib/temple/utils.rb 0000644 0000041 0000041 00000005070 13053463700 016561 0 ustar www-data www-data begin require 'escape_utils' rescue LoadError begin require 'cgi/escape' rescue LoadError end 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 elsif defined?(CGI.escapeHTML) # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) CGI.escapeHTML(html.to_s) 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('::'.freeze, '_'.freeze).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".freeze) if indent_next text = text.gsub("\n".freeze, indent) safe ? text.html_safe : text end end end temple-0.8.0/lib/temple/mixins/ 0000755 0000041 0000041 00000000000 13053463700 016401 5 ustar www-data www-data temple-0.8.0/lib/temple/mixins/grammar_dsl.rb 0000644 0000041 0000041 00000010062 13053463700 021215 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.8.0/lib/temple/mixins/options.rb 0000644 0000041 0000041 00000004411 13053463700 020421 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.8.0/lib/temple/mixins/engine_dsl.rb 0000644 0000041 0000041 00000011463 13053463700 021042 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.8.0/lib/temple/mixins/dispatcher.rb 0000644 0000041 0000041 00000011003 13053463700 021047 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('_'.freeze)[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method end self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def dispatcher(exp) return replace_dispatcher(exp) if self.class != #{self.class} #{tree.compile.gsub("\n", "\n ")} end RUBY 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".freeze, "\n ".freeze) << "\n".freeze 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.8.0/lib/temple/mixins/template.rb 0000644 0000041 0000041 00000001271 13053463700 020542 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.8.0/lib/temple/filters/ 0000755 0000041 0000041 00000000000 13053463700 016542 5 ustar www-data www-data temple-0.8.0/lib/temple/filters/static_merger.rb 0000644 0000041 0000041 00000001430 13053463700 021715 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.8.0/lib/temple/filters/escapable.rb 0000644 0000041 0000041 00000002271 13053463700 021010 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.8.0/lib/temple/filters/dynamic_inliner.rb 0000644 0000041 0000041 00000004235 13053463700 022237 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.8.0/lib/temple/filters/code_merger.rb 0000644 0000041 0000041 00000001224 13053463700 021341 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.8.0/lib/temple/filters/eraser.rb 0000644 0000041 0000041 00000000770 13053463700 020354 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.8.0/lib/temple/filters/static_analyzer.rb 0000644 0000041 0000041 00000001302 13053463700 022257 0 ustar www-data www-data module Temple module Filters # Convert [:dynamic, code] to [:static, text] if code is static Ruby expression. class StaticAnalyzer < Filter def call(exp) # Optimize only when Ripper is available. if ::Temple::StaticAnalyzer.available? super else exp end end def on_dynamic(code) if ::Temple::StaticAnalyzer.static?(code) exp = [:static, eval(code).to_s] newlines = code.count("\n") if newlines == 0 exp else [:multi, exp, *newlines.times.map { [:newline] }] end else [:dynamic, code] end end end end end temple-0.8.0/lib/temple/filters/multi_flattener.rb 0000644 0000041 0000041 00000001043 13053463700 022263 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.8.0/lib/temple/filters/encoding.rb 0000644 0000041 0000041 00000001153 13053463700 020655 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.8.0/lib/temple/filters/remove_bom.rb 0000644 0000041 0000041 00000000460 13053463700 021221 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)), ''.freeze) end end end end temple-0.8.0/lib/temple/filters/string_splitter.rb 0000644 0000041 0000041 00000006674 13053463700 022340 0 ustar www-data www-data begin require 'ripper' rescue LoadError end module Temple module Filters # Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']] class StringSplitter < Filter if defined?(Ripper) && RUBY_VERSION >= "2.0.0" class << self # `code` param must be valid string literal def compile(code) [].tap do |exps| tokens = Ripper.lex(code.strip) tokens.pop while tokens.last && [:on_comment, :on_sp].include?(tokens.last[1]) if tokens.size < 2 raise(FilterError, "Expected token size >= 2 but got: #{tokens.size}") end compile_tokens!(exps, tokens) end end private def strip_quotes!(tokens) _, type, beg_str = tokens.shift if type != :on_tstring_beg raise(FilterError, "Expected :on_tstring_beg but got: #{type}") end _, type, end_str = tokens.pop if type != :on_tstring_end raise(FilterError, "Expected :on_tstring_end but got: #{type}") end [beg_str, end_str] end def compile_tokens!(exps, tokens) beg_str, end_str = strip_quotes!(tokens) until tokens.empty? _, type, str = tokens.shift case type when :on_tstring_content exps << [:static, eval("#{beg_str}#{str}#{end_str}").to_s] when :on_embexpr_beg embedded = shift_balanced_embexpr(tokens) exps << [:dynamic, embedded] unless embedded.empty? end end end def shift_balanced_embexpr(tokens) String.new.tap do |embedded| embexpr_open = 1 until tokens.empty? _, type, str = tokens.shift case type when :on_embexpr_beg embexpr_open += 1 when :on_embexpr_end embexpr_open -= 1 break if embexpr_open == 0 end embedded << str end end end end def on_dynamic(code) return [:dynamic, code] unless string_literal?(code) return [:dynamic, code] if code.include?("\n") temple = [:multi] StringSplitter.compile(code).each do |type, content| case type when :static temple << [:static, content] when :dynamic temple << on_dynamic(content) end end temple end private def string_literal?(code) return false if SyntaxChecker.syntax_error?(code) type, instructions = Ripper.sexp(code) return false if type != :program return false if instructions.size > 1 type, _ = instructions.first type == :string_literal end class SyntaxChecker < Ripper class ParseError < StandardError; end def self.syntax_error?(code) self.new(code).parse false rescue ParseError true end private def on_parse_error(*) raise ParseError end end else # Do nothing if ripper is unavailable def call(ast) ast end end end end end temple-0.8.0/lib/temple/filters/control_flow.rb 0000644 0000041 0000041 00000002222 13053463700 021574 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.8.0/lib/temple/filters/validator.rb 0000644 0000041 0000041 00000000440 13053463700 021052 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.8.0/lib/temple/map.rb 0000644 0000041 0000041 00000004141 13053463700 016174 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.8.0/lib/temple/parser.rb 0000644 0000041 0000041 00000000174 13053463700 016715 0 ustar www-data www-data module Temple # Temple base parser # @api public class Parser include Utils include Mixins::Options end end temple-0.8.0/lib/temple/erb/ 0000755 0000041 0000041 00000000000 13053463700 015642 5 ustar www-data www-data temple-0.8.0/lib/temple/erb/trimming.rb 0000644 0000041 0000041 00000001173 13053463700 020017 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.8.0/lib/temple/erb/parser.rb 0000644 0000041 0000041 00000002117 13053463700 017464 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.8.0/lib/temple/erb/engine.rb 0000644 0000041 0000041 00000000472 13053463700 017437 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.8.0/lib/temple/erb/template.rb 0000644 0000041 0000041 00000000353 13053463700 020003 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.8.0/lib/temple/templates/ 0000755 0000041 0000041 00000000000 13053463700 017070 5 ustar www-data www-data temple-0.8.0/lib/temple/templates/tilt.rb 0000644 0000041 0000041 00000002157 13053463700 020376 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.8.0/lib/temple/templates/rails.rb 0000644 0000041 0000041 00000001547 13053463700 020536 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.8.0/lib/temple/engine.rb 0000644 0000041 0000041 00000003142 13053463700 016664 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.8.0/lib/temple/static_analyzer.rb 0000644 0000041 0000041 00000003165 13053463700 020620 0 ustar www-data www-data begin require 'ripper' rescue LoadError end module Temple module StaticAnalyzer STATIC_TOKENS = [ :on_tstring_beg, :on_tstring_end, :on_tstring_content, :on_embexpr_beg, :on_embexpr_end, :on_lbracket, :on_rbracket, :on_qwords_beg, :on_words_sep, :on_qwords_sep, :on_lparen, :on_rparen, :on_lbrace, :on_rbrace, :on_label, :on_int, :on_float, :on_imaginary, :on_comma, :on_sp, :on_ignored_nl, ].freeze DYNAMIC_TOKENS = [ :on_ident, :on_period, ].freeze STATIC_KEYWORDS = [ 'true', 'false', 'nil', ].freeze STATIC_OPERATORS = [ '=>', ].freeze class << self def available? defined?(Ripper) end def static?(code) return false if code.nil? || code.strip.empty? return false if syntax_error?(code) Ripper.lex(code).each do |_, token, str| case token when *STATIC_TOKENS # noop when :on_kw return false unless STATIC_KEYWORDS.include?(str) when :on_op return false unless STATIC_OPERATORS.include?(str) when *DYNAMIC_TOKENS return false else return false end end true end def syntax_error?(code) SyntaxChecker.new(code).parse false rescue SyntaxChecker::ParseError true end end if defined?(Ripper) class SyntaxChecker < Ripper class ParseError < StandardError; end private def on_parse_error(*) raise ParseError end end end end end temple-0.8.0/lib/temple/filter.rb 0000644 0000041 0000041 00000000233 13053463700 016702 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.8.0/lib/temple/version.rb 0000644 0000041 0000041 00000000046 13053463700 017104 0 ustar www-data www-data module Temple VERSION = '0.8.0' end temple-0.8.0/lib/temple/generators/ 0000755 0000041 0000041 00000000000 13053463700 017243 5 ustar www-data www-data temple-0.8.0/lib/temple/generators/rails_output_buffer.rb 0000644 0000041 0000041 00000002034 13053463700 023652 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('; '.freeze) 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.8.0/lib/temple/generators/string_buffer.rb 0000644 0000041 0000041 00000000642 13053463700 022431 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.8.0/lib/temple/generators/erb.rb 0000644 0000041 0000041 00000000771 13053463700 020345 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.8.0/lib/temple/generators/array.rb 0000644 0000041 0000041 00000000515 13053463700 020707 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.8.0/lib/temple/generators/array_buffer.rb 0000644 0000041 0000041 00000001323 13053463700 022236 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 freeze = options[:freeze_static] ? '.freeze' : '' "#{buffer} = #{buffer}.join(\"\"#{freeze})" end end end end temple-0.8.0/lib/temple/templates.rb 0000644 0000041 0000041 00000000405 13053463700 017414 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.8.0/lib/temple/exceptions.rb 0000644 0000041 0000041 00000000417 13053463700 017602 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.8.0/lib/temple/generator.rb 0000644 0000041 0000041 00000003502 13053463700 017405 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('; '.freeze) 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.8.0/test/ 0000755 0000041 0000041 00000000000 13053463700 014015 5 ustar www-data www-data temple-0.8.0/test/html/ 0000755 0000041 0000041 00000000000 13053463700 014761 5 ustar www-data www-data temple-0.8.0/test/html/test_attribute_remover.rb 0000644 0000041 0000041 00000002732 13053463700 022113 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.8.0/test/html/test_pretty.rb 0000644 0000041 0000041 00000003752 13053463700 017703 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 = /