lib/ejs.rb
# EJS (Embedded JavaScript) template compiler for Ruby # This is a port of Underscore.js' `_.template` function: # http://documentcloud.github.com/underscore/ module EJS JS_UNESCAPES = { '\\' => '\\', "'" => "'", 'r' => "\r", 'n' => "\n", 't' => "\t", 'u2028' => "\u2028", 'u2029' => "\u2029" } JS_ESCAPES = JS_UNESCAPES.invert JS_UNESCAPE_PATTERN = /\\(#{Regexp.union(JS_UNESCAPES.keys)})/ JS_ESCAPE_PATTERN = Regexp.union(JS_ESCAPES.keys) class << self attr_accessor :evaluation_pattern attr_accessor :interpolation_pattern attr_accessor :escape_pattern # Compiles an EJS template to a JavaScript function. The compiled # function takes an optional argument, an object specifying local # variables in the template. You can optionally pass the # `:evaluation_pattern` and `:interpolation_pattern` options to # `compile` if you want to specify a different tag syntax for the # template. # # EJS.compile("Hello <%= name %>") # # => "function(obj){...}" # def compile(source, options = {}) source = source.dup js_escape!(source) replace_escape_tags!(source, options) replace_interpolation_tags!(source, options) replace_evaluation_tags!(source, options) "function(obj){var __p=[],print=function(){__p.push.apply(__p,arguments);};" + "with(obj||{}){__p.push('#{source}');}return __p.join('');}" end # Evaluates an EJS template with the given local variables and # compiler options. You will need the ExecJS # (https://github.com/sstephenson/execjs/) library and a # JavaScript runtime available. # # EJS.evaluate("Hello <%= name %>", :name => "world") # # => "Hello world" # def evaluate(template, locals = {}, options = {}) require "execjs" context = ExecJS.compile("var evaluate = #{compile(template, options)}") context.call("evaluate", locals) end protected def js_escape!(source) source.gsub!(JS_ESCAPE_PATTERN) { |match| '\\' + JS_ESCAPES[match] } source end def js_unescape!(source) source.gsub!(JS_UNESCAPE_PATTERN) { |match| JS_UNESCAPES[match[1..-1]] } source end def replace_escape_tags!(source, options) source.gsub!(options[:escape_pattern] || escape_pattern) do "',(''+#{js_unescape!($1)})#{escape_function},'" end end def replace_evaluation_tags!(source, options) source.gsub!(options[:evaluation_pattern] || evaluation_pattern) do "'); #{js_unescape!($1)}; __p.push('" end end def replace_interpolation_tags!(source, options) source.gsub!(options[:interpolation_pattern] || interpolation_pattern) do "', #{js_unescape!($1)},'" end end def escape_function ".replace(/&/g, '&')" + ".replace(/</g, '<')" + ".replace(/>/g, '>')" + ".replace(/\"/g, '"')" + ".replace(/'/g, ''')" + ".replace(/\\//g,'/')" end end self.evaluation_pattern = /<%([\s\S]+?)%>/ self.interpolation_pattern = /<%=([\s\S]+?)%>/ self.escape_pattern = /<%-([\s\S]+?)%>/ end