temple-0.8.2/0000755000175000017500000000000013537476665012377 5ustar josephjosephtemple-0.8.2/.gitignore0000644000175000017500000000004213537476665014363 0ustar josephjosephcoverage .yardoc doc Gemfile.lock temple-0.8.2/lib/0000755000175000017500000000000013537476665013145 5ustar josephjosephtemple-0.8.2/lib/temple.rb0000644000175000017500000000563213537476665014766 0ustar josephjosephrequire '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.2/lib/temple/0000755000175000017500000000000013537476665014433 5ustar josephjosephtemple-0.8.2/lib/temple/utils.rb0000644000175000017500000000507013537476665016122 0ustar josephjosephbegin 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.2/lib/temple/version.rb0000644000175000017500000000004613537476665016445 0ustar josephjosephmodule Temple VERSION = '0.8.2' end temple-0.8.2/lib/temple/templates/0000755000175000017500000000000013537476665016431 5ustar josephjosephtemple-0.8.2/lib/temple/templates/rails.rb0000644000175000017500000000160113537476665020066 0ustar josephjosephmodule Temple module Templates class Rails extend Mixins::Template def call(template, source = nil) opts = {}.update(self.class.options).update(file: template.identifier) self.class.compile((source || 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.2/lib/temple/templates/tilt.rb0000644000175000017500000000215713537476665017737 0ustar josephjosephrequire '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.2/lib/temple/templates.rb0000644000175000017500000000040513537476665016755 0ustar josephjosephmodule 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.2/lib/temple/mixins/0000755000175000017500000000000013537476665015742 5ustar josephjosephtemple-0.8.2/lib/temple/mixins/grammar_dsl.rb0000644000175000017500000001006213537476665020556 0ustar josephjosephmodule 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.2/lib/temple/mixins/options.rb0000644000175000017500000000441113537476665017762 0ustar josephjosephmodule 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.2/lib/temple/mixins/engine_dsl.rb0000644000175000017500000001146313537476665020403 0ustar josephjosephmodule 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.2/lib/temple/mixins/dispatcher.rb0000644000175000017500000001104213537476665020413 0ustar josephjosephmodule 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 = String.new 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.2/lib/temple/mixins/template.rb0000644000175000017500000000127113537476665020103 0ustar josephjosephmodule 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.2/lib/temple/filters/0000755000175000017500000000000013537476665016103 5ustar josephjosephtemple-0.8.2/lib/temple/filters/encoding.rb0000644000175000017500000000115313537476665020216 0ustar josephjosephmodule 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.2/lib/temple/filters/remove_bom.rb0000644000175000017500000000046013537476665020562 0ustar josephjosephmodule 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.2/lib/temple/filters/string_splitter.rb0000644000175000017500000000673013537476665021672 0ustar josephjosephbegin 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" && Ripper.respond_to?(:lex) 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.2/lib/temple/filters/eraser.rb0000644000175000017500000000077013537476665017715 0ustar josephjosephmodule 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.2/lib/temple/filters/code_merger.rb0000644000175000017500000000122413537476665020702 0ustar josephjosephmodule 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.2/lib/temple/filters/dynamic_inliner.rb0000644000175000017500000000423513537476665021600 0ustar josephjosephmodule 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.2/lib/temple/filters/static_merger.rb0000644000175000017500000000143013537476665021256 0ustar josephjosephmodule 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.2/lib/temple/filters/escapable.rb0000644000175000017500000000227113537476665020351 0ustar josephjosephmodule 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.2/lib/temple/filters/validator.rb0000644000175000017500000000044013537476665020413 0ustar josephjosephmodule 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.2/lib/temple/filters/multi_flattener.rb0000644000175000017500000000104313537476665021624 0ustar josephjosephmodule 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.2/lib/temple/filters/static_analyzer.rb0000644000175000017500000000130213537476665021620 0ustar josephjosephmodule 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.2/lib/temple/filters/control_flow.rb0000644000175000017500000000222213537476665021135 0ustar josephjosephmodule 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.2/lib/temple/generators/0000755000175000017500000000000013537476665016604 5ustar josephjosephtemple-0.8.2/lib/temple/generators/string_buffer.rb0000644000175000017500000000064213537476665021772 0ustar josephjosephmodule 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.2/lib/temple/generators/rails_output_buffer.rb0000644000175000017500000000203413537476665023213 0ustar josephjosephmodule 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.2/lib/temple/generators/erb.rb0000644000175000017500000000077113537476665017706 0ustar josephjosephmodule 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.2/lib/temple/generators/array_buffer.rb0000644000175000017500000000132313537476665021577 0ustar josephjosephmodule 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.2/lib/temple/generators/array.rb0000644000175000017500000000051513537476665020250 0ustar josephjosephmodule 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.2/lib/temple/map.rb0000644000175000017500000000414113537476665015535 0ustar josephjosephmodule 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.2/lib/temple/erb/0000755000175000017500000000000013537476665015203 5ustar josephjosephtemple-0.8.2/lib/temple/erb/template.rb0000644000175000017500000000035313537476665017344 0ustar josephjosephmodule 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.2/lib/temple/erb/parser.rb0000644000175000017500000000211713537476665017025 0ustar josephjosephmodule 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.2/lib/temple/erb/engine.rb0000644000175000017500000000047213537476665017000 0ustar josephjosephmodule 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.2/lib/temple/erb/trimming.rb0000644000175000017500000000117313537476665017360 0ustar josephjosephmodule 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.2/lib/temple/static_analyzer.rb0000644000175000017500000000322113537476665020152 0ustar josephjosephbegin 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) && Ripper.respond_to?(:lex) 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.2/lib/temple/html/0000755000175000017500000000000013537476665015377 5ustar josephjosephtemple-0.8.2/lib/temple/html/attribute_merger.rb0000644000175000017500000000251613537476665021274 0ustar josephjosephmodule 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.2/lib/temple/html/attribute_sorter.rb0000644000175000017500000000113713537476665021327 0ustar josephjosephmodule 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.2/lib/temple/html/dispatcher.rb0000644000175000017500000000134313537476665020053 0ustar josephjosephmodule 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.2/lib/temple/html/fast.rb0000644000175000017500000001170713537476665016667 0ustar josephjosephmodule 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, ""] 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.2/lib/temple/html/attribute_remover.rb0000644000175000017500000000175713537476665021500 0ustar josephjosephmodule 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.2/lib/temple/html/filter.rb0000644000175000017500000000066413537476665017217 0ustar josephjosephmodule 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.2/lib/temple/html/safe.rb0000644000175000017500000000052413537476665016643 0ustar josephjosephmodule 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.2/lib/temple/html/pretty.rb0000644000175000017500000000641613537476665017262 0ustar josephjosephmodule 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 : ''}"] 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.2/lib/temple/exceptions.rb0000644000175000017500000000041713537476665017143 0ustar josephjosephmodule 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.2/lib/temple/parser.rb0000644000175000017500000000017413537476665016256 0ustar josephjosephmodule Temple # Temple base parser # @api public class Parser include Utils include Mixins::Options end end temple-0.8.2/lib/temple/filter.rb0000644000175000017500000000023313537476665016243 0ustar josephjosephmodule Temple # Temple base filter # @api public class Filter include Utils include Mixins::Dispatcher include Mixins::Options end end temple-0.8.2/lib/temple/engine.rb0000644000175000017500000000314213537476665016225 0ustar josephjosephmodule 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.2/lib/temple/grammar.rb0000644000175000017500000000317513537476665016414 0ustar josephjosephmodule 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.2/lib/temple/generator.rb0000644000175000017500000000350213537476665016746 0ustar josephjosephmodule 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.2/test/0000755000175000017500000000000013537476665013356 5ustar josephjosephtemple-0.8.2/test/helper.rb0000644000175000017500000000147013537476665015164 0ustar josephjosephrequire 'bacon' require 'temple' module TestHelper def with_html_safe require 'temple/html/safe' String.send(:define_method, :html_safe?) { false } String.send(:define_method, :html_safe) { Temple::HTML::SafeString.new(self) } yield ensure String.send(:undef_method, :html_safe?) if String.method_defined?(:html_safe?) String.send(:undef_method, :html_safe) if String.method_defined?(:html_safe) end def grammar_validate(grammar, exp, message) lambda { grammar.validate!(exp) }.should.raise(Temple::InvalidExpression).message.should.equal message end def erb(src, options = {}) Temple::ERB::Template.new(options) { src }.render end def erubis(src, options = {}) Tilt::ErubisTemplate.new(options) { src }.render end end class Bacon::Context include TestHelper end temple-0.8.2/test/test_erb.rb0000644000175000017500000000175513537476665015522 0ustar josephjosephrequire 'helper' require 'tilt/erubis' describe Temple::ERB::Engine do it 'should compile erb' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } erb(src).should.equal erubis(src) end it 'should recognize comments' do src = %q{ hello <%# comment -- ignored -- useful in testing %> world} erb(src).should.equal erubis(src) end it 'should recognize <%% and %%>' do src = %q{ <%% <% if true %> %%> <% end %> } erb(src).should.equal "\n<%\n %>\n" end it 'should escape automatically' do src = '<%= "<" %>' ans = '<' erb(src).should.equal ans end it 'should support == to disable automatic escape' do src = '<%== "<" %>' ans = '<' erb(src).should.equal ans end it 'should support trim mode' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } erb(src, trim: true).should.equal erubis(src, trim: true) erb(src, trim: false).should.equal erubis(src, trim: false) end end temple-0.8.2/test/test_filter.rb0000644000175000017500000000141013537476665016223 0ustar josephjosephrequire 'helper' class SimpleFilter < Temple::Filter define_options :key def on_test(arg) [:on_test, arg] end end describe Temple::Filter do it 'should support options' do Temple::Filter.should.respond_to :default_options Temple::Filter.should.respond_to :set_default_options Temple::Filter.should.respond_to :define_options Temple::Filter.new.options.should.be.instance_of Temple::ImmutableMap SimpleFilter.new(key: 3).options[:key].should.equal 3 end it 'should implement call' do Temple::Filter.new.call([:exp]).should.equal [:exp] end it 'should process expressions' do filter = SimpleFilter.new filter.call([:unhandled]).should.equal [:unhandled] filter.call([:test, 42]).should.equal [:on_test, 42] end end temple-0.8.2/test/test_map.rb0000644000175000017500000000164613537476665015526 0ustar josephjosephrequire 'helper' describe Temple::ImmutableMap do it 'has read accessor' do hash = Temple::ImmutableMap.new({a: 1},{b: 2, a: 3}) hash[:a].should.equal 1 hash[:b].should.equal 2 end it 'has include?' do hash = Temple::ImmutableMap.new({a: 1},{b: 2, a: 3}) hash.should.include :a hash.should.include :b hash.should.not.include :c end it 'has values' do Temple::ImmutableMap.new({a: 1},{b: 2, a: 3}).values.sort.should.equal [1,2] end it 'has keys' do Temple::ImmutableMap.new({a: 1},{b: 2, a: 3}).keys.should.equal [:a,:b] end it 'has to_a' do Temple::ImmutableMap.new({a: 1},{b: 2, a: 3}).to_a.should.equal [[:a, 1], [:b, 2]] end end describe Temple::MutableMap do it 'has write accessor' do parent = {a: 1} hash = Temple::MutableMap.new(parent) hash[:a].should.equal 1 hash[:a] = 2 hash[:a].should.equal 2 parent[:a].should.equal 1 end end temple-0.8.2/test/test_static_analyzer.rb0000644000175000017500000000244313537476665020141 0ustar josephjosephrequire 'helper' describe Temple::StaticAnalyzer do describe '.available?' do it 'should return true if its dependency is available' do Temple::StaticAnalyzer.available?.should.equal(defined?(Ripper) && Ripper.respond_to?(:lex)) end end if Temple::StaticAnalyzer.available? describe '.static?' do it 'should return true if given Ruby expression is static' do ['true', 'false', '"hello world"', "[1, { 2 => 3 }]", "[\n1,\n]"].each do |exp| Temple::StaticAnalyzer.static?(exp).should.equal(true) end end it 'should return false if given Ruby expression is dynamic' do ['1 + 2', 'variable', 'method_call(a)', 'CONSTANT'].each do |exp| Temple::StaticAnalyzer.static?(exp).should.equal(false) end end end describe '.syntax_error?' do it 'should return false if given Ruby expression is valid' do ['Foo.bar.baz { |c| c.d! }', '{ foo: bar }'].each do |exp| Temple::StaticAnalyzer.syntax_error?(exp).should.equal(false) end end it 'should return true if given Ruby expression is invalid' do ['Foo.bar.baz { |c| c.d! ', ' foo: bar '].each do |exp| Temple::StaticAnalyzer.syntax_error?(exp).should.equal(true) end end end end end temple-0.8.2/test/mixins/0000755000175000017500000000000013537476665014665 5ustar josephjosephtemple-0.8.2/test/mixins/test_grammar_dsl.rb0000644000175000017500000000443013537476665020542 0ustar josephjosephrequire 'helper' module BasicGrammar extend Temple::Mixins::GrammarDSL Expression << Symbol | Answer | [:zero_or_more, 'Expression*'] | [:one_or_more, 'Expression+'] | [:zero_or_one, 'Expression?'] | [:bool, Bool] | nil Bool << true | false Answer << Value(42) end module ExtendedGrammar extend BasicGrammar Expression << [:extended, Expression] end describe Temple::Mixins::GrammarDSL do it 'should support class types' do BasicGrammar.should.match :symbol BasicGrammar.should.not.match [:symbol] BasicGrammar.should.not.match 'string' BasicGrammar.should.not.match ['string'] end it 'should support value types' do BasicGrammar.should.match 42 BasicGrammar.should.not.match 43 end it 'should support nesting' do BasicGrammar.should.match [:zero_or_more, [:zero_or_more]] end it 'should support *' do BasicGrammar.should.match [:zero_or_more] BasicGrammar.should.match [:zero_or_more, nil, 42] end it 'should support +' do BasicGrammar.should.not.match [:one_or_more] BasicGrammar.should.match [:one_or_more, 42] BasicGrammar.should.match [:one_or_more, 42, nil] end it 'should support ?' do BasicGrammar.should.not.match [:zero_or_one, nil, 42] BasicGrammar.should.match [:zero_or_one] BasicGrammar.should.match [:zero_or_one, 42] end it 'should support extended grammars' do ExtendedGrammar.should.match [:extended, [:extended, 42]] BasicGrammar.should.not.match [:zero_or_more, [:extended, nil]] BasicGrammar.should.not.match [:extended, [:extended, 42]] end it 'should have validate!' do grammar_validate BasicGrammar, [:zero_or_more, [:zero_or_more, [:unknown]]], "BasicGrammar::Expression did not match\n[:unknown]\n" grammar_validate BasicGrammar, [:zero_or_more, [:one_or_more]], "BasicGrammar::Expression did not match\n[:one_or_more]\n" grammar_validate BasicGrammar, [:zero_or_more, 123, [:unknown]], "BasicGrammar::Expression did not match\n123\n" grammar_validate BasicGrammar, [:bool, 123], "BasicGrammar::Bool did not match\n123\n" end end temple-0.8.2/test/mixins/test_dispatcher.rb0000644000175000017500000000314313537476665020400 0ustar josephjosephrequire 'helper' class FilterWithDispatcherMixin include Temple::Mixins::Dispatcher def on_test(arg) [:on_test, arg] end def on_test_check(arg) [:on_check, arg] end def on_second_test(arg) [:on_second_test, arg] end def on_a_b(*arg) [:on_ab, *arg] end def on_a_b_test(arg) [:on_ab_test, arg] end def on_a_b_c_d_test(arg) [:on_abcd_test, arg] end end class FilterWithDispatcherMixinAndOn < FilterWithDispatcherMixin def on(*args) [:on_zero, *args] end end describe Temple::Mixins::Dispatcher do before do @filter = FilterWithDispatcherMixin.new end it 'should return unhandled expressions' do @filter.call([:unhandled]).should.equal [:unhandled] end it 'should dispatch first level' do @filter.call([:test, 42]).should.equal [:on_test, 42] end it 'should dispatch second level' do @filter.call([:second, :test, 42]).should.equal [:on_second_test, 42] end it 'should dispatch second level if prefixed' do @filter.call([:test, :check, 42]).should.equal [:on_check, 42] end it 'should dispatch parent level' do @filter.call([:a, 42]).should == [:a, 42] @filter.call([:a, :b, 42]).should == [:on_ab, 42] @filter.call([:a, :b, :test, 42]).should == [:on_ab_test, 42] @filter.call([:a, :b, :c, 42]).should == [:on_ab, :c, 42] @filter.call([:a, :b, :c, :d, 42]).should == [:on_ab, :c, :d, 42] @filter.call([:a, :b, :c, :d, :test, 42]).should == [:on_abcd_test, 42] end it 'should dispatch zero level' do FilterWithDispatcherMixinAndOn.new.call([:foo,42]).should == [:on_zero, :foo, 42] end end temple-0.8.2/test/test_grammar.rb0000644000175000017500000000413713537476665016375 0ustar josephjosephrequire 'helper' describe Temple::Grammar do it 'should match core expressions' do Temple::Grammar.should.match [:multi] Temple::Grammar.should.match [:multi, [:multi]] Temple::Grammar.should.match [:static, 'Text'] Temple::Grammar.should.match [:dynamic, 'Text'] Temple::Grammar.should.match [:code, 'Text'] Temple::Grammar.should.match [:capture, 'Text', [:multi]] Temple::Grammar.should.match [:newline] end it 'should not match invalid core expressions' do Temple::Grammar.should.not.match [:multi, 'String'] Temple::Grammar.should.not.match [:static] Temple::Grammar.should.not.match [:dynamic, 1] Temple::Grammar.should.not.match [:code, :sym] Temple::Grammar.should.not.match [:capture, [:multi]] Temple::Grammar.should.not.match [:newline, [:multi]] end it 'should match control flow expressions' do Temple::Grammar.should.match [:if, 'Condition', [:multi]] Temple::Grammar.should.match [:if, 'Condition', [:multi], [:multi]] Temple::Grammar.should.match [:block, 'Loop', [:multi]] Temple::Grammar.should.match [:case, 'Arg', ['Cond1', [:multi]], ['Cond1', [:multi]], [:else, [:multi]]] Temple::Grammar.should.not.match [:case, 'Arg', [:sym, [:multi]]] Temple::Grammar.should.match [:cond, ['Cond1', [:multi]], ['Cond2', [:multi]], [:else, [:multi]]] Temple::Grammar.should.not.match [:cond, [:sym, [:multi]]] end it 'should match escape expression' do Temple::Grammar.should.match [:escape, true, [:multi]] Temple::Grammar.should.match [:escape, false, [:multi]] end it 'should match html expressions' do Temple::Grammar.should.match [:html, :doctype, 'Doctype'] Temple::Grammar.should.match [:html, :comment, [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi], [:multi]] Temple::Grammar.should.match [:html, :tag, 'Tag', [:multi], [:static, 'Text']] Temple::Grammar.should.match [:html, :tag, 'Tag', [:html, :attrs, [:html, :attr, 'id', [:static, 'val']]], [:static, 'Text']] end end temple-0.8.2/test/filters/0000755000175000017500000000000013537476665015026 5ustar josephjosephtemple-0.8.2/test/filters/test_escapable.rb0000644000175000017500000000250313537476665020331 0ustar josephjosephrequire 'helper' describe Temple::Filters::Escapable do before do @filter = Temple::Filters::Escapable.new end it 'should handle escape expressions' do @filter.call([:escape, true, [:multi, [:static, "a < b"], [:dynamic, "ruby_method"]] ]).should.equal [:multi, [:static, "a < b"], [:dynamic, "::Temple::Utils.escape_html((ruby_method))"], ] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] @filter.call(exp).should.equal exp end it 'should keep statics intact' do exp = [:multi, [:static, '<']] @filter.call(exp).should.equal exp end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] @filter.call(exp).should.equal exp end it 'should have use_html_safe option' do with_html_safe do filter = Temple::Filters::Escapable.new(use_html_safe: true) filter.call([:escape, true, [:static, Temple::HTML::SafeString.new("a < b")] ]).should.equal [:static, "a < b"] end end it 'should support censoring' do filter = Temple::Filters::Escapable.new(escape_code: '(%s).gsub("Temple sucks", "Temple rocks")') filter.call([:escape, true, [:static, "~~ Temple sucks ~~"] ]).should.equal [:static, "~~ Temple rocks ~~"] end end temple-0.8.2/test/filters/test_static_analyzer.rb0000644000175000017500000000206213537476665021606 0ustar josephjosephrequire 'helper' describe Temple::Filters::StaticAnalyzer do before do @filter = Temple::Filters::StaticAnalyzer.new @generator = Temple::Generator.new end if Temple::StaticAnalyzer.available? it 'should convert :dynamic to :static if code is static' do @filter.call([:dynamic, '"#{"hello"}#{100}"'] ).should.equal [:static, 'hello100'] end it 'should not convert :dynamic if code is dynamic' do exp = [:dynamic, '"#{hello}#{100}"'] @filter.call(exp).should.equal(exp) end it 'should not change number of newlines in generated code' do exp = [:dynamic, "[100,\n200,\n]"] @filter.call(exp).should.equal([:multi, [:static, '[100, 200]'], [:newline], [:newline]]) @generator.call(@filter.call(exp)).count("\n"). should.equal(@generator.call(exp).count("\n")) end else it 'should do nothing' do [ [:dynamic, '"#{"hello"}#{100}"'], [:dynamic, '"#{hello}#{100}"'], ].each do |exp| @filter.call(exp).should.equal(exp) end end end end temple-0.8.2/test/filters/test_static_merger.rb0000644000175000017500000000173313537476665021246 0ustar josephjosephrequire 'helper' describe Temple::Filters::StaticMerger do before do @filter = Temple::Filters::StaticMerger.new end it 'should merge serveral statics' do @filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:static, "Good night"] ]).should.equal [:static, "Hello World, Good night"] end it 'should merge serveral statics around code' do @filter.call([:multi, [:static, "Hello "], [:static, "World!"], [:code, "123"], [:static, "Good night, "], [:static, "everybody"] ]).should.equal [:multi, [:static, "Hello World!"], [:code, "123"], [:static, "Good night, everybody"] ] end it 'should merge serveral statics across newlines' do @filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:newline], [:static, "Good night"] ]).should.equal [:multi, [:static, "Hello World, Good night"], [:newline] ] end end temple-0.8.2/test/filters/test_multi_flattener.rb0000644000175000017500000000130013537476665021602 0ustar josephjosephrequire 'helper' describe Temple::Filters::MultiFlattener do before do @filter = Temple::Filters::MultiFlattener.new end it 'should flatten nested multi expressions' do @filter.call([:multi, [:static, "a"], [:multi, [:dynamic, "aa"], [:multi, [:static, "aaa"], [:static, "aab"], ], [:dynamic, "ab"], ], [:static, "b"], ]).should.equal [:multi, [:static, "a"], [:dynamic, "aa"], [:static, "aaa"], [:static, "aab"], [:dynamic, "ab"], [:static, "b"], ] end it 'should return first element' do @filter.call([:multi, [:code, 'foo']]).should.equal [:code, 'foo'] end end temple-0.8.2/test/filters/test_dynamic_inliner.rb0000644000175000017500000000473613537476665021570 0ustar josephjosephrequire 'helper' describe Temple::Filters::DynamicInliner do before do @filter = Temple::Filters::DynamicInliner.new end it 'should compile several statics into dynamic' do @filter.call([:multi, [:static, "Hello "], [:static, "World\n "], [:static, "Have a nice day"] ]).should.equal [:dynamic, '"Hello World\n Have a nice day"'] end it 'should compile several dynamics into dynamic' do @filter.call([:multi, [:dynamic, "@hello"], [:dynamic, "@world"], [:dynamic, "@yeah"] ]).should.equal [:dynamic, '"#{@hello}#{@world}#{@yeah}"'] end it 'should compile static and dynamic into dynamic' do @filter.call([:multi, [:static, "Hello"], [:dynamic, "@world"], [:dynamic, "@yeah"], [:static, "Nice"] ]).should.equal [:dynamic, '"Hello#{@world}#{@yeah}Nice"'] end it 'should merge statics and dynamics around a code' do exp = @filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:code, "Oh yeah"], [:dynamic, "@yeah"], [:static, "Once more"] ]).should.equal [:multi, [:dynamic, '"Hello #{@world}"'], [:code, "Oh yeah"], [:dynamic, '"#{@yeah}Once more"'] ] end it 'should keep codes intact' do @filter.call([:multi, [:code, 'foo']]).should.equal [:code, 'foo'] end it 'should keep single statics intact' do @filter.call([:multi, [:static, 'foo']]).should.equal [:static, 'foo'] end it 'should keep single dynamic intact' do @filter.call([:multi, [:dynamic, 'foo']]).should.equal [:dynamic, 'foo'] end it 'should inline inside multi' do @filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:multi, [:static, "Hello "], [:dynamic, "@world"]], [:static, "Hello "], [:dynamic, "@world"] ]).should.equal [:multi, [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'] ] end it 'should merge across newlines' do exp = @filter.call([:multi, [:static, "Hello \n"], [:newline], [:dynamic, "@world"], [:newline] ]).should.equal [:dynamic, ['"Hello \n"', '"#{@world}"', '""'].join("\\\n")] end it 'should compile static followed by newline' do @filter.call([:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ]).should.equal [:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ] end end temple-0.8.2/test/filters/test_string_splitter.rb0000644000175000017500000000121113537476665021641 0ustar josephjosephrequire 'helper' begin require 'ripper' rescue LoadError end if defined?(Ripper) && RUBY_VERSION >= "2.0.0" describe Temple::Filters::StringSplitter do before do @filter = Temple::Filters::StringSplitter.new end it 'should split :dynamic with string literal' do @filter.call([:dynamic, '"static#{dynamic}"'] ).should.equal [:multi, [:static, 'static'], [:dynamic, 'dynamic']] end describe '.compile' do it 'should raise CompileError for non-string literals' do lambda { Temple::Filters::StringSplitter.compile('1') }. should.raise(Temple::FilterError) end end end end temple-0.8.2/test/filters/test_eraser.rb0000644000175000017500000000176713537476665017706 0ustar josephjosephrequire 'helper' describe Temple::Filters::Eraser do it 'should respect keep' do eraser = Temple::Filters::Eraser.new(keep: [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:a], [:multi], [:multi] ] end it 'should respect erase' do eraser = Temple::Filters::Eraser.new(erase: [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:multi], [:b], [:c] ] end it 'should choose erase over keep' do eraser = Temple::Filters::Eraser.new(keep: [:a, :b], erase: [:a]) eraser.call([:multi, [:a], [:b], [:c] ]).should.equal [:multi, [:multi], [:b], [:multi] ] end it 'should erase nested types' do eraser = Temple::Filters::Eraser.new(erase: [[:a, :b]]) eraser.call([:multi, [:a, :a], [:a, :b], [:b] ]).should.equal [:multi, [:a, :a], [:multi], [:b] ] end end temple-0.8.2/test/filters/test_code_merger.rb0000644000175000017500000000142413537476665020666 0ustar josephjosephrequire 'helper' describe Temple::Filters::CodeMerger do before do @filter = Temple::Filters::CodeMerger.new end it 'should merge serveral codes' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:code, "c"] ]).should.equal [:code, "a; b; c"] end it 'should merge serveral codes around static' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:static, "123"], [:code, "a"], [:code, "b"] ]).should.equal [:multi, [:code, "a; b"], [:static, "123"], [:code, "a; b"] ] end it 'should merge serveral codes with newlines' do @filter.call([:multi, [:code, "a"], [:code, "b"], [:newline], [:code, "c"] ]).should.equal [:code, "a; b\nc"] end end temple-0.8.2/test/filters/test_control_flow.rb0000644000175000017500000000365513537476665021132 0ustar josephjosephrequire 'helper' describe Temple::Filters::ControlFlow do before do @filter = Temple::Filters::ControlFlow.new end it 'should process blocks' do @filter.call([:block, 'loop do', [:static, 'Hello'] ]).should.equal [:multi, [:code, 'loop do'], [:static, 'Hello'], [:code, 'end']] end it 'should process if' do @filter.call([:if, 'condition', [:static, 'Hello'] ]).should.equal [:multi, [:code, 'if condition'], [:static, 'Hello'], [:code, 'end'] ] end it 'should process if with else' do @filter.call([:if, 'condition', [:static, 'True'], [:static, 'False'] ]).should.equal [:multi, [:code, 'if condition'], [:static, 'True'], [:code, 'else'], [:static, 'False'], [:code, 'end'] ] end it 'should create elsif' do @filter.call([:if, 'condition1', [:static, '1'], [:if, 'condition2', [:static, '2'], [:static, '3']] ]).should.equal [:multi, [:code, 'if condition1'], [:static, '1'], [:code, 'elsif condition2'], [:static, '2'], [:code, 'else'], [:static, '3'], [:code, 'end'] ] end it 'should process cond' do @filter.call([:cond, ['cond1', [:exp1]], ['cond2', [:exp2]], [:else, [:exp3]], ]).should.equal [:multi, [:code, 'case'], [:code, 'when cond1'], [:exp1], [:code, 'when cond2'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end it 'should process case' do @filter.call([:case, 'var', ['Array', [:exp1]], ['String', [:exp2]], [:else, [:exp3]], ]).should.equal [:multi, [:code, 'case (var)'], [:code, 'when Array'], [:exp1], [:code, 'when String'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end end temple-0.8.2/test/test_engine.rb0000644000175000017500000001250213537476665016207 0ustar josephjoseph# -*- coding: utf-8 -*- require 'helper' class Callable1 def call(exp) exp end end class Callable2 def call(exp) exp end end class MySpecialFilter def initialize(opts = {}) end def call(exp) exp end end class TestEngine < Temple::Engine use(:Parser) do |input| [:static, input] end use :MyFilter1, proc {|exp| exp } use :MyFilter2, proc {|exp| exp } use Temple::HTML::Pretty, pretty: true filter :MultiFlattener generator :ArrayBuffer use(:BeforeBeforeLast) { MySpecialFilter } use :BeforeLast, Callable1.new use(:Last) { Callable2.new } end describe Temple::Engine do it 'should build chain' do TestEngine.chain.size.should.equal 9 TestEngine.chain[0].first.should.equal :Parser TestEngine.chain[0].size.should.equal 2 TestEngine.chain[0].last.should.be.instance_of Proc TestEngine.chain[1].first.should.equal :MyFilter1 TestEngine.chain[1].size.should.equal 2 TestEngine.chain[1].last.should.be.instance_of Proc TestEngine.chain[2].first.should.equal :MyFilter2 TestEngine.chain[2].size.should.equal 2 TestEngine.chain[2].last.should.be.instance_of Proc TestEngine.chain[3].first.should.equal :'Temple::HTML::Pretty' TestEngine.chain[3].size.should.equal 2 TestEngine.chain[3].last.should.be.instance_of Proc TestEngine.chain[4].first.should.equal :MultiFlattener TestEngine.chain[4].size.should.equal 2 TestEngine.chain[4].last.should.be.instance_of Proc TestEngine.chain[5].first.should.equal :ArrayBuffer TestEngine.chain[5].size.should.equal 2 TestEngine.chain[5].last.should.be.instance_of Proc TestEngine.chain[6].first.should.equal :BeforeBeforeLast TestEngine.chain[6].size.should.equal 2 TestEngine.chain[6].last.should.be.instance_of Proc TestEngine.chain[7].first.should.equal :BeforeLast TestEngine.chain[7].size.should.equal 2 TestEngine.chain[7].last.should.be.instance_of Proc TestEngine.chain[8].first.should.equal :Last TestEngine.chain[8].size.should.equal 2 TestEngine.chain[8].last.should.be.instance_of Proc end it 'should instantiate chain' do call_chain = TestEngine.new.send(:call_chain) call_chain[0].should.be.instance_of Method call_chain[1].should.be.instance_of Method call_chain[2].should.be.instance_of Method call_chain[3].should.be.instance_of Temple::HTML::Pretty call_chain[4].should.be.instance_of Temple::Filters::MultiFlattener call_chain[5].should.be.instance_of Temple::Generators::ArrayBuffer call_chain[6].should.be.instance_of MySpecialFilter call_chain[7].should.be.instance_of Callable1 call_chain[8].should.be.instance_of Callable2 end it 'should have #append' do engine = TestEngine.new call_chain = engine.send(:call_chain) call_chain.size.should.equal 9 engine.append :MyFilter3 do |exp| exp end TestEngine.chain.size.should.equal 9 engine.chain.size.should.equal 10 engine.chain[9].first.should.equal :MyFilter3 engine.chain[9].size.should.equal 2 engine.chain[9].last.should.be.instance_of Proc call_chain = engine.send(:call_chain) call_chain.size.should.equal 10 call_chain[9].should.be.instance_of Method end it 'should have #prepend' do engine = TestEngine.new call_chain = engine.send(:call_chain) call_chain.size.should.equal 9 engine.prepend :MyFilter0 do |exp| exp end TestEngine.chain.size.should.equal 9 engine.chain.size.should.equal 10 engine.chain[0].first.should.equal :MyFilter0 engine.chain[0].size.should.equal 2 engine.chain[0].last.should.be.instance_of Proc engine.chain[1].first.should.equal :Parser call_chain = engine.send(:call_chain) call_chain.size.should.equal 10 call_chain[0].should.be.instance_of Method end it 'should have #after' do engine = TestEngine.new engine.after :Parser, :MyFilter0 do |exp| exp end TestEngine.chain.size.should.equal 9 engine.chain.size.should.equal 10 engine.chain[0].first.should.equal :Parser engine.chain[1].first.should.equal :MyFilter0 engine.chain[2].first.should.equal :MyFilter1 end it 'should have #before' do engine = TestEngine.new engine.before :MyFilter1, :MyFilter0 do |exp| exp end TestEngine.chain.size.should.equal 9 engine.chain.size.should.equal 10 engine.chain[0].first.should.equal :Parser engine.chain[1].first.should.equal :MyFilter0 engine.chain[2].first.should.equal :MyFilter1 end it 'should have #remove' do engine = TestEngine.new engine.remove :MyFilter1 TestEngine.chain.size.should.equal 9 engine.chain.size.should.equal 8 engine.chain[0].first.should.equal :Parser engine.chain[1].first.should.equal :MyFilter2 engine = TestEngine.new engine.remove /Last/ engine.chain.size.should.equal 6 end it 'should have #replace' do engine = TestEngine.new engine.replace :Parser, :MyParser do |exp| exp end engine.chain.size.should.equal 9 engine.chain[0].first.should.equal :MyParser end it 'should work with inheritance' do inherited_engine = Class.new(TestEngine) inherited_engine.chain.size.should.equal 9 inherited_engine.append :MyFilter3 do |exp| exp end inherited_engine.chain.size.should.equal 10 TestEngine.chain.size.should.equal 9 end end temple-0.8.2/test/test_generator.rb0000644000175000017500000001361713537476665016740 0ustar josephjosephrequire 'helper' class SimpleGenerator < Temple::Generator def preamble "#{buffer} = BUFFER" end def postamble buffer end def on_static(s) concat "S:#{s}" end def on_dynamic(s) concat "D:#{s}" end def on_code(s) "C:#{s}" end end describe Temple::Generator do it 'should compile simple expressions' do gen = SimpleGenerator.new gen.call([:static, 'test']).should.equal '_buf = BUFFER; _buf << (S:test); _buf' gen.call([:dynamic, 'test']).should.equal '_buf = BUFFER; _buf << (D:test); _buf' gen.call([:code, 'test']).should.equal '_buf = BUFFER; C:test; _buf' end it 'should compile multi expression' do gen = SimpleGenerator.new(buffer: "VAR") gen.call([:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"] ]).should.equal 'VAR = BUFFER; VAR << (S:static); VAR << (D:dynamic); C:code; VAR' end it 'should compile capture' do gen = SimpleGenerator.new(buffer: "VAR", capture_generator: SimpleGenerator) gen.call([:capture, "foo", [:static, "test"] ]).should.equal 'VAR = BUFFER; foo = BUFFER; foo << (S:test); foo; VAR' end it 'should compile capture with multi' do gen = SimpleGenerator.new(buffer: "VAR", capture_generator: SimpleGenerator) gen.call([:multi, [:static, "before"], [:capture, "foo", [:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"]]], [:static, "after"] ]).should.equal 'VAR = BUFFER; VAR << (S:before); foo = BUFFER; foo << (S:static); ' + 'foo << (D:dynamic); C:code; foo; VAR << (S:after); VAR' end it 'should compile newlines' do gen = SimpleGenerator.new(buffer: "VAR") gen.call([:multi, [:static, "static"], [:newline], [:dynamic, "dynamic"], [:newline], [:code, "code"] ]).should.equal "VAR = BUFFER; VAR << (S:static); \n; " + "VAR << (D:dynamic); \n; C:code; VAR" end end describe Temple::Generators::Array do it 'should compile simple expressions' do gen = Temple::Generators::Array.new(freeze_static: false) gen.call([:static, 'test']).should.equal '_buf = []; _buf << ("test"); _buf' gen.call([:dynamic, 'test']).should.equal '_buf = []; _buf << (test); _buf' gen.call([:code, 'test']).should.equal '_buf = []; test; _buf' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf' end it 'should freeze static' do gen = Temple::Generators::Array.new(freeze_static: true) gen.call([:static, 'test']).should.equal '_buf = []; _buf << ("test".freeze); _buf' end end describe Temple::Generators::ArrayBuffer do it 'should compile simple expressions' do gen = Temple::Generators::ArrayBuffer.new(freeze_static: false) gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:dynamic, 'test']).should.equal '_buf = (test).to_s' gen.call([:code, 'test']).should.equal '_buf = []; test; _buf = _buf.join("")' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << ("b"); _buf = _buf.join("")' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = []; _buf << ("a"); _buf << (b); _buf = _buf.join("")' end it 'should freeze static' do gen = Temple::Generators::ArrayBuffer.new(freeze_static: true) gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:multi, [:dynamic, '1'], [:static, 'test']]).should.equal '_buf = []; _buf << (1); _buf << ("test".freeze); _buf = _buf.join("".freeze)' end end describe Temple::Generators::StringBuffer do it 'should compile simple expressions' do gen = Temple::Generators::StringBuffer.new(freeze_static: false) gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:dynamic, 'test']).should.equal '_buf = (test).to_s' gen.call([:code, 'test']).should.equal '_buf = \'\'; test; _buf' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal '_buf = \'\'; _buf << ("a"); _buf << ("b"); _buf' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal '_buf = \'\'; _buf << ("a"); _buf << ((b).to_s); _buf' end it 'should freeze static' do gen = Temple::Generators::StringBuffer.new(freeze_static: true) gen.call([:static, 'test']).should.equal '_buf = "test"' gen.call([:multi, [:dynamic, '1'], [:static, 'test']]).should.equal '_buf = \'\'; _buf << ((1).to_s); _buf << ("test".freeze); _buf' end end describe Temple::Generators::ERB do it 'should compile simple expressions' do gen = Temple::Generators::ERB.new gen.call([:static, 'test']).should.equal 'test' gen.call([:dynamic, 'test']).should.equal '<%= test %>' gen.call([:code, 'test']).should.equal '<% test %>' gen.call([:multi, [:static, 'a'], [:static, 'b']]).should.equal 'ab' gen.call([:multi, [:static, 'a'], [:dynamic, 'b']]).should.equal 'a<%= b %>' end end describe Temple::Generators::RailsOutputBuffer do it 'should compile simple expressions' do gen = Temple::Generators::RailsOutputBuffer.new(freeze_static: false) gen.call([:static, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + '@output_buffer.safe_concat(("test")); @output_buffer' gen.call([:dynamic, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + '@output_buffer.safe_concat(((test).to_s)); @output_buffer' gen.call([:code, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; ' + 'test; @output_buffer' end it 'should freeze static' do gen = Temple::Generators::RailsOutputBuffer.new(freeze_static: true) gen.call([:static, 'test']).should.equal '@output_buffer = ActiveSupport::SafeBuffer.new; @output_buffer.safe_concat(("test".freeze)); @output_buffer' end end temple-0.8.2/test/html/0000755000175000017500000000000013537476665014322 5ustar josephjosephtemple-0.8.2/test/html/test_attribute_merger.rb0000644000175000017500000000575013537476665021261 0ustar josephjosephrequire 'helper' describe Temple::HTML::AttributeMerger do before do @merger = Temple::HTML::AttributeMerger.new end it 'should pass static attributes through' do @merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:html, :attrs, [:html, :attr, "class", [:static, "b"]]], [:content]] end it 'should preserve the order of html attributes' do @merger.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]]] # Use case: @merger.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]]] end it 'should merge ids' do @merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:dynamic, 'a']], [:html, :attr, 'id', [:dynamic, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:html, :attrs, [:html, :attr, "id", [:multi, [:code, "_temple_html_attributemerger1 = []"], [:capture, "_temple_html_attributemerger1[0]", [:dynamic, "a"]], [:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]], [:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\"_\")"]]]], [:content]] end it 'should merge classes' do @merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'a']], [:html, :attr, 'class', [:dynamic, 'b']]], [:content] ]).should.equal [:html, :tag, "div", [:html, :attrs, [:html, :attr, "class", [:multi, [:code, "_temple_html_attributemerger1 = []"], [:capture, "_temple_html_attributemerger1[0]", [:static, "a"]], [:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]], [:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\" \")"]]]], [:content]] end end temple-0.8.2/test/html/test_pretty.rb0000644000175000017500000000375213537476665017244 0ustar josephjosephrequire '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 = /']] @html.call([:multi, [:html, :doctype, 'html']]).should.equal [:multi, [:static, '']] @html.call([:multi, [:html, :doctype, '1.1']]).should.equal [:multi, [:static, '']] end it 'should compile xml encoding' do @html.call([:html, :doctype, 'xml latin1']).should.equal [:static, ""] end it 'should compile html comment' do @html.call([:html, :comment, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should compile js wrapped in comments' do Temple::HTML::Fast.new(js_wrapper: nil).call([:html, :js, [:static, 'test']]).should.equal [:static, "test"] Temple::HTML::Fast.new(js_wrapper: :comment).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] Temple::HTML::Fast.new(js_wrapper: :cdata).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, "\n//\n"]] Temple::HTML::Fast.new(js_wrapper: :both).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should guess default js comment' do Temple::HTML::Fast.new(js_wrapper: :guess, format: :xhtml).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, "\n//\n"]] Temple::HTML::Fast.new(js_wrapper: :guess, format: :html).call([:html, :js, [:static, 'test']]).should.equal [:multi, [:static, ""]] end it 'should compile autoclosed html tag' do @html.call([:html, :tag, 'img', [:attrs], [:multi, [:newline]] ]).should.equal [:multi, [:static, ""], [:multi, [:newline]]] end it 'should compile explicitly closed html tag' do @html.call([:html, :tag, 'closed', [:attrs] ]).should.equal [:multi, [:static, ""]] end it 'should compile html with content' do @html.call([:html, :tag, 'div', [:attrs], [:content] ]).should.equal [:multi, [:static, ""], [:content], [:static, ""]] end it 'should compile html with attrs' do @html.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:static, 'test']], [:html, :attr, 'class', [:dynamic, 'block']]], [:content] ]).should.equal [:multi, [:static, ""], [:content], [:static, ""]] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] @html.call(exp).should.equal exp end it 'should keep statics intact' do exp = [:multi, [:static, '<']] @html.call(exp).should.equal exp end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] @html.call(exp).should.equal exp end end temple-0.8.2/test/html/test_attribute_sorter.rb0000644000175000017500000000373113537476665021313 0ustar josephjosephrequire 'helper' describe Temple::HTML::AttributeSorter do before do @ordered = Temple::HTML::AttributeSorter.new @unordered = Temple::HTML::AttributeSorter.new sort_attrs: false end it 'should sort html attributes by name by default, when :sort_attrs is true' do @ordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']], [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']]]] end it 'should preserve the order of html attributes when :sort_attrs is false' do @unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]]] # Use case: @unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]] ]).should.equal [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]]] end end temple-0.8.2/test/html/test_attribute_remover.rb0000644000175000017500000000273213537476665021454 0ustar josephjosephrequire '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.2/test/test_utils.rb0000644000175000017500000000220413537476665016100 0ustar josephjosephrequire 'helper' class UniqueTest include Temple::Utils end describe Temple::Utils do it 'has empty_exp?' do Temple::Utils.empty_exp?([:multi]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi]]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi, [:newline]], [:newline]]).should.be.true Temple::Utils.empty_exp?([:multi]).should.be.true Temple::Utils.empty_exp?([:multi, [:multi, [:static, 'text']]]).should.be.false Temple::Utils.empty_exp?([:multi, [:newline], [:multi, [:dynamic, 'text']]]).should.be.false end it 'has unique_name' do u = UniqueTest.new u.unique_name.should.equal '_uniquetest1' u.unique_name.should.equal '_uniquetest2' UniqueTest.new.unique_name.should.equal '_uniquetest1' end it 'has escape_html' do Temple::Utils.escape_html('<').should.equal '<' end it 'should escape unsafe html strings' do with_html_safe do Temple::Utils.escape_html_safe('<').should.equal '<' end end it 'should not escape safe html strings' do with_html_safe do Temple::Utils.escape_html_safe('<'.html_safe).should.equal '<' end end end temple-0.8.2/.yardopts0000644000175000017500000000006613537476665014247 0ustar josephjoseph--title Temple --files EXPRESSIONS.md,CHANGES,LICENSE temple-0.8.2/Rakefile0000644000175000017500000000100413537476665014037 0ustar josephjosephrequire '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.2/Gemfile0000644000175000017500000000012113537476665013664 0ustar josephjosephsource 'https://rubygems.org/' gemspec gem 'escape_utils' if ENV['ESCAPE_UTILS'] temple-0.8.2/CHANGES0000644000175000017500000001426213537476665013377 0ustar josephjoseph0.8.2 * Support TruffleRuby in Temple::Filters::StaticAnalyzer (#127) * Support TruffleRuby in Temple::Filters::StringSplitter (#127) 0.8.1 * Stop relying on deprecated method in Rails (#121) * Fix issue with --enable-frozen-string-literal * Escape html in markdown 0.8.0 * Add Temple::StaticAnalyzer to analyze Ruby expressions * Support newlines in Temple::Filters::StaticAnalyzer 0.7.8 * Fix an warning in StaticAnalyzer 0.7.7 * Add Temple::Filters::StaticAnalyzer, Temple::Filters::StringSplitter * Freeze string literals 0.7.6 * EngineDSL - add support for use(:Filter) { FilterClassName } 0.7.5 * HTML::Pretty Fix indentation issue (https://github.com/slim-template/slim-rails/issues/78) 0.7.4 * EngineDSL: allow to replace/remove with regexp * Fix deprecation warning (#83) 0.7.3 * Temple::ERB::Trimming - replace option trim_mode with trim and switch to erubis-like trimming 0.7.2 * Remove Filters::StaticFreezer, the generator does the freezing 0.7.1 * Rename *Hash to *Map * Add Filters::StaticFreezer 0.7.0 * Drop Ruby 1.8.7 support * EngineDSL: Remove option filter * HTML: Deprecate :html4, :html5 formats * HTML: Add format :xml * Rename DefaultOptions to ClassOptions * Deprecate default_options in favor of options * Add Utils.indent_dynamic 0.6.10 * Tilt template: Support :outvar and save/restore buffer to make the behaviour compatible with ERB 0.6.9 * HTML::Pretty: Fix wrong line numbers * Tilt template: Don't overwrite buffer always * Generator: add preamble and postamble which do nothing * Tilt template: don't overwrite streaming option * OptionHash: inherit valid keys * temple/html/safe: add poor man's html_safe? implementation (not required automatically) * Temple::Mixins::GrammarDSL - Add some missing match? methods * Temple::Utils.escape_html_safe - Add parameter safe 0.6.8 * HTML::Fast add svg doctype * Render standalone html 5 attributes 0.6.7 * HTML::Pretty - change some block level tags * Reduce memory allocations in immutable hash 0.6.6 * Use default encoding utf-8 * Escape also ' * Try to load escape_utils by default 0.6.5 * Added Filters::CodeMerger * Added Filters::Encoding * Added Filters::RemoveBOM * Added Generators::ERB 0.6.4 * Check for ActionView instead of Rails (#72) 0.6.3 * Fix HTML escaping for HTML::Pretty (Issue #69) 0.6.2 * [:html, :js, code] abstraction added 0.6.1 * HTML::Pretty improved 0.6.0 * HTML::AttributeMerger: rename option :attr_delimiter to :merge_attrs * HTML: rename option :attr_wrapper to :attr_quote 0.5.5 * HTML pretty: Do not remove empty lines, add newline after doctype 0.5.4 * HTML::AttributeMerger fixed, it didn't remove first empty attribute values * Add HTML::AttributeRemover back, :remove_empty_attrs must be an Array of Strings now of the attributes to be removed if empty * Simplify [:case] expression grammar * Ignore parameter :outvar by sinatra since sinatra assumes also that the buffer is a String, they should set :buffer and :generator explicitly if they need the access 0.5.3 * Only print an message if invalid options are passed to Temple filters or engines since many libraries seem to use Slim and Temple in an incorrect way 0.5.2 * Fix the :outvar problem really 0.5.1 * Support Sinatra :outvar option in Tilt template 0.5.0 * Added exception Temple::FilterError which should be thrown by filters * Added Temple::Parser as default base class for parsers * escape_html doesn't escape / anymore * HTML::AttributeSorter uses stable sorting now * HTML::AttributeRemover removed (Was too Slim specific) * Engine option :chain removed * Option validation implemented (Use define_options in your filters) * Deprecated options implemented (Use deprecated_options in your filters) * ThreadOptions added, Method #with_options 0.4.1 * Generators: produce optimized code * remove deprecated method EngineDSL#wildcard * Set tilt template default_mime_type to text/html * HTML: Support conditional comments [:html, :condcomment, ...] 0.4.0 * Split Temple::HTML::AttributeMerger in AttributeSorter, AttributeMerger and AttributeRemover * Fix issue #58 0.3.5 * Temple::HTML::Pretty improved * :sort_attrs option (default: true) added to HTML::AttributeMerger; if set to false, the attributes will appear in the insertion order * Temple::Mixins::EngineDSL api changed ("wildcard" is deprecated, use "use" instead) * Temple::Mixins::CompiledDispatcher supports arbitrary levels now * Don't use gsub! on incoming strings (#57) * Fix newlines in erb parser (#46) 0.3.4 * Bugfix release (0.3.3 was yanked) 0.3.3 * Support for rails 3.1 streaming * Add EngineDSL#wildcard * HTML::Fast/Pretty supports only :xhtml and :html formats from now on * HTML::AttributeMerger extracted from HTML::Fast 0.3.1, 0.3.2 * Don't modify strings destructively with gsub! in HTML::Pretty. This doesn't work with Rails safe buffers in version >= 3.0.8. 0.3.0 * Compiled expression dispatching * Method temple_dispatch is obsolete * EscapeHTML renamed to Escapable * Control flow filter added * HTML filter: Tag and attribute expressions changed * Expression grammar added * Expression validator added * Debugger filter removed (Validator is better replacement) 0.2.0 * Add mutable/immutable hashes for option inheritance * Rails template support added * Rename Filter#compile to Filter#call * Engine chain reconfiguration (append, prepend, replace, ...) * HTML filter: Don't output empty attributes * Escape expression changed [:escape, true/false, Expression] 0.1.8 * HTML filter: Support :format => :html (alias for :html5) 0.1.7 * HTML::Pretty indents dynamic content only if it doesn't contain preformatted tags 0.1.6 * Flexible chain building 0.1.5 * Default options for engines 0.1.4 * HTML::Pretty added * Tilt-based template class added * Escaping filter added * Filter base class added * Fix capturing (Issue #15) 0.1.3 * Close issue #10 * Refactoring 0.1.2 * Add HTML filter * Remove Escapable filter * Add method for checking if expression is empty 0.1.1 * Test added 0.1.0 * Initial release temple-0.8.2/.travis.yml0000644000175000017500000000054413537476665014513 0ustar josephjosephlanguage: 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.2/README.md0000644000175000017500000002147313537476665013665 0ustar josephjosephTemple ====== [![Build Status](https://secure.travis-ci.org/judofyr/temple.svg?branch=master)](http://travis-ci.org/judofyr/temple) [![Code Climate](https://codeclimate.com/github/judofyr/temple.svg)](https://codeclimate.com/github/judofyr/temple) [![Gem Version](https://badge.fury.io/rb/temple.svg)](https://rubygems.org/gems/temple) Temple is an abstraction and a framework for compiling templates to pure Ruby. It's all about making it easier to experiment, implement and optimize template languages. If you're interested in implementing your own template language, or anything else related to the internals of a template engine: You've come to the right place. Have a look around, and if you're still wondering: Ask on the mailing list and we'll try to do our best. In fact, it doesn't have to be related to Temple at all. As long as it has something to do with template languages, we're interested: . Links ----- * Source: * Bugs: * List: * API documentation: * Latest Gem: * GitHub master: * Abstractions: Overview -------- Temple is built on a theory that every template consists of three elements: * Static text * Dynamic text (pieces of Ruby which are evaluated and sent to the client) * Codes (pieces of Ruby which are evaluated and *not* sent to the client, but might change the control flow). The goal of a template engine is to take the template and eventually compile it into *the core abstraction*: ```ruby [:multi, [:static, "Hello "], [:dynamic, "@user.name"], [:static, "!\n"], [:code, "if @user.birthday == Date.today"], [:static, "Happy birthday!"], [:code, "end"]] ``` Then you can apply some optimizations, feed it to Temple and it generates fast Ruby code for you: ```ruby _buf = [] _buf << ("Hello #{@user.name}!\n") if @user.birthday == Date.today _buf << "Happy birthday!" end _buf.join ``` S-expression ------------ In Temple, an Sexp is simply an array (or a subclass) where the first element is the *type* and the rest are the *arguments*. The type must be a symbol and it's recommended to only use strings, symbols, arrays and numbers as arguments. Temple uses Sexps to represent templates because it's a simple and straightforward data structure, which can easily be written by hand and manipulated by computers. Some examples: ```ruby [:static, "Hello World!"] [:multi, [:static, "Hello "], [:dynamic, "@world"]] [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]] ``` *NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp` class. While you can use this class (since it's a subclass of Array), it's not what Temple mean by "Sexp". Abstractions ------------ The idea behind Temple is that abstractions are good, and it's better to have too many than too few. While you should always end up with the core abstraction, you shouldn't stress about it. Take one step at a time, and only do one thing at every step. So what's an abstraction? An abstraction is when you introduce a new types: ```ruby # Instead of: [:static, "Use the force"] # You use: [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]] ``` ### Why are abstractions so important? First of all, it means that several template engines can share code. Instead of having two engines which goes all the way to generating HTML, you have two smaller engines which only compiles to the HTML abstraction together with something that compiles the HTML abstraction to the core abstraction. Often you also introduce abstractions because there's more than one way to do it. There's not a single way to generate HTML. Should it be indented? If so, with tabs or spaces? Or should it remove as much whitespace as possible? Single or double quotes in attributes? Escape all weird UTF-8 characters? With an abstraction you can easily introduce a completely new HTML compiler, and whatever is below doesn't have to care about it *at all*. They just continue to use the HTML abstraction. Maybe you even want to write your compiler in another language? Sexps are easily serialized and if you don't mind working across processes, it's not a problem at all. All abstractions used by Temple are documented in [EXPRESSIONS.md](EXPRESSIONS.md). Compilers --------- A *compiler* is simply an object which responds a method called #call which takes one argument and returns a value. It's illegal for a compiler to mutate the argument, and it should be possible to use the same instance several times (although not by several threads at the same time). While a compiler can be any object, you very often want to structure it as a class. Temple then assumes the initializer takes an optional option hash: ```ruby class MyCompiler def initialize(options = {}) @options = options end def call(exp) # do stuff end end ``` ### Parsers In Temple, a parser is also a compiler, because a compiler is just something that takes some input and produces some output. A parser is then something that takes a string and returns an Sexp. It's important to remember that the parser *should be dumb*. No optimization, no guesses. It should produce an Sexp that is as close to the source as possible. You should invent your own abstraction. Maybe you even want to separate the parsers into several parts and introduce several abstractions on the way? ### Filters A filter is a compiler which take an Sexp and returns an Sexp. It might turn convert it one step closer to the core-abstraction, it might create a new abstraction, or it might just optimize in the current abstraction. Ultimately, it's still just a compiler which takes an Sexp and returns an Sexp. For instance, Temple ships with {Temple::Filters::DynamicInliner} and {Temple::Filters::StaticMerger} which are general optimization filters which works on the core abstraction. An HTML compiler would be a filter, since it would take an Sexp in the HTML abstraction and compile it down to the core abstraction. ### Generators A generator is a compiler which takes an Sexp and returns a string which is valid Ruby code. Most of the time you would just use {Temple::Generators::ArrayBuffer} or any of the other generators in {Temple::Generators}, but nothing stops you from writing your own. In fact, one of the great things about Temple is that if you write a new generator which turns out to be a lot faster then the others, it's going to make *every single engine* based on Temple faster! So if you have any ideas, please share them - it's highly appreciated. Engines ------- When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy: ```ruby class MyEngine < Temple::Engine # First run MyParser use MyParser # Then a custom filter use MyFilter # Then some general optimizations filters filter :MultiFlattener filter :StaticMerger filter :DynamicInliner # Finally the generator generator :ArrayBuffer end engine = MyEngine.new(strict: "For MyParser") engine.call(something) ``` And then? --------- You've ran the template through the parser, some filters and in the end a generator. What happens next? Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and Rails. ```ruby require 'tilt' # Create template class MyTemplate and register your file extension MyTemplate = Temple::Templates::Tilt(MyEngine, register_as: 'ext') Tilt.new('example.ext').render # => Render a file MyTemplate.new { "String" }.render # => Render a string ``` Installation ------------ You need at least Ruby 1.9.3 to work with Temple. Temple is published as a Ruby Gem which can be installed as following: ```bash $ gem install temple ``` Engines using Temple -------------------- * [Slim](https://github.com/slim-template/slim) * [Hamlit](https://github.com/k0kubun/hamlit) * [Faml](https://github.com/eagletmt/faml) * [Sal](https://github.com/stonean/sal.rb) * [Temple-Mustache (Example implementation)](https://github.com/minad/temple-mustache) * Temple ERB example implementation (Temple::ERB::Template) * [WLang](https://github.com/blambeau/wlang) Acknowledgements ---------------- Thanks to [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) for creating an excellent template engine (Markaby) which is quite slow. That's how I started experimenting with template engines in the first place. I also owe [Ryan Davis](http://zenspider.com/) a lot for his excellent projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is heavily inspired by how these tools work. temple-0.8.2/LICENSE0000644000175000017500000000203713537476665013406 0ustar josephjosephCopyright (c) 2010 Magnus Holm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. temple-0.8.2/temple.gemspec0000644000175000017500000000200313537476665015225 0ustar josephjoseph# -*- 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.2/EXPRESSIONS.md0000644000175000017500000001642213537476665014550 0ustar josephjosephTemple expression documentation =============================== Temple uses S-expressions to represent the parsed template code. The S-expressions are passed from filter to filter until the generator. The generator transforms the S-expression to a ruby code string. See the {file:README.md README} for an introduction. In this document we documented all the expressions which are used by Temple. There is also a formal grammar which can validate expressions. The Core Abstraction -------------------- The core abstraction is what every template evetually should be compiled to. Currently it consists of six types: multi, static, dynamic, code, newline and capture. When compiling, there's two different strings we'll have to think about. First we have the generated code. This is what your engine (from Temple's point of view) spits out. If you construct this carefully enough, you can make exceptions report correct line numbers, which is very convenient. Then there's the result. This is what your engine (from the user's point of view) spits out. It's what happens if you evaluate the generated code. ### [:multi, *sexp] Multi is what glues everything together. It's simply a sexp which combines several others sexps: [:multi, [:static, "Hello "], [:dynamic, "@world"]] ### [:static, string] Static indicates that the given string should be appended to the result. Example: [:static, "Hello World"] is the same as: _buf << "Hello World" [:static, "Hello \n World"] is the same as _buf << "Hello\nWorld" ### [:dynamic, ruby] Dynamic indicates that the given Ruby code should be evaluated and then appended to the result. The Ruby code must be a complete expression in the sense that you can pass it to eval() and it would not raise SyntaxError. Example: [:dynamic, 'Math::PI * r**2'] ### [:code, ruby] Code indicates that the given Ruby code should be evaluated, and may change the control flow. Any \n causes a newline in the generated code. Example: [:code, 'area = Math::PI * r**2'] ### [:newline] Newline causes a newline in the generated code, but not in the result. ### [:capture, variable_name, sexp] Evaluates the Sexp using the rules above, but instead of appending to the result, it sets the content to the variable given. Example: [:multi, [:static, "Some content"], [:capture, "foo", [:static, "More content"]], [:dynamic, "foo.downcase"]] is the same as: _buf << "Some content" foo = "More content" _buf << foo.downcase Control flow abstraction ------------------------ Control flow abstractions can be used to write common ruby control flow constructs. These expressions are compiled to [:code, ruby] by Temple::Filters::ControlFlow ### [:if, condition, if-sexp, optional-else-sexp] Example: [:if, "1+1 == 2", [:static, "Yes"], [:static, "No"]] is the same as: if 1+1 == 2 _buf << "Yes" else _buf << "No" end ### [:block, ruby, sexp] Example: [:block, '10.times do', [:static, 'Hello']] is the same as: 10.times do _buf << 'Hello' end ### [:case, argument, [condition, sexp], [condition, sexp], ...] Example: [:case, 'value', ["1", "value is 1"], ["2", "value is 2"], [:else, "don't know"]] is the same as: case value when 1 _buf << "value is 1" when 2 _buf << "value is 2" else _buf << "don't know" end ### [:cond, [condition, sexp], [condition, sexp], ...] [:cond, ["a", "a is true"], ["b", "b is true"], [:else, "a and b are false"]] is the same as: case when a _buf << "a is true" when b _buf << "b is true" else _buf << "a and b are false" end Escape abstraction ------------------ The Escape abstraction is processed by Temple::Filters::Escapable. ### [:escape, bool, sexp] The boolean flag switches escaping on or off for the content sexp. Dynamic and static expressions are manipulated. Example: [:escape, true, [:multi, [:dynamic, "code"], [:static, "<"], [:escape, false, [:static, ">"]]]] is transformed to [:multi, [:dynamic, 'escape_html(code)'], [:static, '<'], [:static, '>']] HTML abstraction ---------------- The HTML abstraction is processed by the html filters (Temple::HTML::Fast and Temple::HTML::Pretty). ### [:html, :doctype, string] Example: [:html, :doctype, '5'] generates Supported doctypes:
NameGenerated doctype
1.1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
html, 5<!DOCTYPE html>
strict<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
frameset<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
mobile<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
basic<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
transitional<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
### [:html, :comment, sexp] Example: [:html, :comment, [:static, 'comment']] generates: ### [:html, :condcomment, condition, sexp] Example: [:html, :condcomment, 'IE', [:static, 'comment']] generates: ### [:html, :tag, identifier, attributes, optional-sexp] HTML tag abstraction. Identifier can be a String or a Symbol. If the optional content Sexp is omitted the tag is closed (e.g. `
` ``). The tag is also closed if the content Sexp is empty (consists only of :multi and :newline expressions) and the tag is registered as auto-closing. Example: [:html, :tag, 'img', [:html, :attrs, [:html, :attr, 'src', 'image.png']]] [:html, :tag, 'p', [:multi], [:static, 'Content']] generates:

Content

### [:html, :attrs, attributes] List of html attributes [:html, :attr, identifier, sexp] ### [:html, :attr, identifier, sexp] HTML attribute abstraction. Identifier can be a String or a Symbol. ### [:html, :js, code] HTML javascript abstraction which wraps the js code in a HTML comment or CDATA depending on document format. Formal grammar -------------- Validate expressions with Temple::Grammar.match? and Temple::Grammar.validate! Temple::Grammar.match? [:multi, [:static, 'Valid Temple Expression']] Temple::Grammar.validate! [:multi, 'Invalid Temple Expression'] The formal grammar is given in a Ruby DSL similar to EBNF and should be easy to understand if you know EBNF. Repeated tokens are given by appending ?, * or + as in regular expressions. * ? means zero or one occurence * \* means zero or more occurences * \+ means one or more occurences