├── Rakefile ├── ejs.gemspec ├── LICENSE ├── README.md ├── lib └── ejs.rb └── test └── test_ejs.rb /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | 3 | task :default => :test 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.warning = true 8 | end 9 | -------------------------------------------------------------------------------- /ejs.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "ejs" 3 | s.version = "1.1.1" 4 | s.summary = "EJS (Embedded JavaScript) template compiler" 5 | s.description = "Compile and evaluate EJS (Embedded JavaScript) templates from Ruby." 6 | 7 | s.files = Dir["README.md", "LICENSE", "lib/**/*.rb"] 8 | 9 | s.add_development_dependency "execjs", "~> 0.4" 10 | 11 | s.authors = ["Sam Stephenson"] 12 | s.email = ["sstephenson@gmail.com"] 13 | s.homepage = "https://github.com/sstephenson/ruby-ejs/" 14 | end 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Sam Stephenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EJS (Embedded JavaScript) template compiler for Ruby 2 | ==================================================== 3 | 4 | EJS templates embed JavaScript code inside `<% ... %>` tags, much like 5 | ERB. This library is a port of 6 | [Underscore.js](http://documentcloud.github.com/underscore/)'s 7 | [`_.template` 8 | function](http://documentcloud.github.com/underscore/#template) to 9 | Ruby, and strives to maintain the same syntax and semantics. 10 | 11 | Pass an EJS template to `EJS.compile` to generate a JavaScript 12 | function: 13 | 14 | EJS.compile("Hello <%= name %>") 15 | # => "function(obj){...}" 16 | 17 | Invoke the function in a JavaScript environment to produce a string 18 | value. You can pass an optional object specifying local variables for 19 | template evaluation. 20 | 21 | The EJS tag syntax is as follows: 22 | 23 | * `<% ... %>` silently evaluates the statement inside the tags. 24 | * `<%= ... %>` evaluates the expression inside the tags and inserts 25 | its string value into the template output. 26 | * `<%- ... %>` behaves like `<%= ... %>` but HTML-escapes its output. 27 | 28 | If you have the [ExecJS](https://github.com/sstephenson/execjs/) 29 | library and a suitable JavaScript runtime installed, you can pass a 30 | template and an optional hash of local variables to `EJS.evaluate`: 31 | 32 | EJS.evaluate("Hello <%= name %>", :name => "world") 33 | # => "Hello world" 34 | 35 | ----- 36 | 37 | © 2012 Sam Stephenson 38 | 39 | Released under the MIT license 40 | -------------------------------------------------------------------------------- /lib/ejs.rb: -------------------------------------------------------------------------------- 1 | # EJS (Embedded JavaScript) template compiler for Ruby 2 | # This is a port of Underscore.js' `_.template` function: 3 | # http://documentcloud.github.com/underscore/ 4 | 5 | module EJS 6 | JS_UNESCAPES = { 7 | '\\' => '\\', 8 | "'" => "'", 9 | 'r' => "\r", 10 | 'n' => "\n", 11 | 't' => "\t", 12 | 'u2028' => "\u2028", 13 | 'u2029' => "\u2029" 14 | } 15 | JS_ESCAPES = JS_UNESCAPES.invert 16 | JS_UNESCAPE_PATTERN = /\\(#{Regexp.union(JS_UNESCAPES.keys)})/ 17 | JS_ESCAPE_PATTERN = Regexp.union(JS_ESCAPES.keys) 18 | 19 | class << self 20 | attr_accessor :evaluation_pattern 21 | attr_accessor :interpolation_pattern 22 | attr_accessor :escape_pattern 23 | 24 | # Compiles an EJS template to a JavaScript function. The compiled 25 | # function takes an optional argument, an object specifying local 26 | # variables in the template. You can optionally pass the 27 | # `:evaluation_pattern` and `:interpolation_pattern` options to 28 | # `compile` if you want to specify a different tag syntax for the 29 | # template. 30 | # 31 | # EJS.compile("Hello <%= name %>") 32 | # # => "function(obj){...}" 33 | # 34 | def compile(source, options = {}) 35 | source = source.dup 36 | 37 | js_escape!(source) 38 | replace_escape_tags!(source, options) 39 | replace_interpolation_tags!(source, options) 40 | replace_evaluation_tags!(source, options) 41 | "function(obj){var __p=[],print=function(){__p.push.apply(__p,arguments);};" + 42 | "with(obj||{}){__p.push('#{source}');}return __p.join('');}" 43 | end 44 | 45 | # Evaluates an EJS template with the given local variables and 46 | # compiler options. You will need the ExecJS 47 | # (https://github.com/sstephenson/execjs/) library and a 48 | # JavaScript runtime available. 49 | # 50 | # EJS.evaluate("Hello <%= name %>", :name => "world") 51 | # # => "Hello world" 52 | # 53 | def evaluate(template, locals = {}, options = {}) 54 | require "execjs" 55 | context = ExecJS.compile("var evaluate = #{compile(template, options)}") 56 | context.call("evaluate", locals) 57 | end 58 | 59 | protected 60 | def js_escape!(source) 61 | source.gsub!(JS_ESCAPE_PATTERN) { |match| '\\' + JS_ESCAPES[match] } 62 | source 63 | end 64 | 65 | def js_unescape!(source) 66 | source.gsub!(JS_UNESCAPE_PATTERN) { |match| JS_UNESCAPES[match[1..-1]] } 67 | source 68 | end 69 | 70 | def replace_escape_tags!(source, options) 71 | source.gsub!(options[:escape_pattern] || escape_pattern) do 72 | "',(''+#{js_unescape!($1)})#{escape_function},'" 73 | end 74 | end 75 | 76 | def replace_evaluation_tags!(source, options) 77 | source.gsub!(options[:evaluation_pattern] || evaluation_pattern) do 78 | "'); #{js_unescape!($1)}; __p.push('" 79 | end 80 | end 81 | 82 | def replace_interpolation_tags!(source, options) 83 | source.gsub!(options[:interpolation_pattern] || interpolation_pattern) do 84 | "', #{js_unescape!($1)},'" 85 | end 86 | end 87 | 88 | def escape_function 89 | ".replace(/&/g, '&')" + 90 | ".replace(//g, '>')" + 92 | ".replace(/\"/g, '"')" + 93 | ".replace(/'/g, ''')" + 94 | ".replace(/\\//g,'/')" 95 | end 96 | end 97 | 98 | self.evaluation_pattern = /<%([\s\S]+?)%>/ 99 | self.interpolation_pattern = /<%=([\s\S]+?)%>/ 100 | self.escape_pattern = /<%-([\s\S]+?)%>/ 101 | end 102 | -------------------------------------------------------------------------------- /test/test_ejs.rb: -------------------------------------------------------------------------------- 1 | require "ejs" 2 | require "test/unit" 3 | 4 | FUNCTION_PATTERN = /^function\s*\(.*?\)\s*\{(.*?)\}$/ 5 | 6 | BRACE_SYNTAX = { 7 | :evaluation_pattern => /\{\{([\s\S]+?)\}\}/, 8 | :interpolation_pattern => /\{\{=([\s\S]+?)\}\}/, 9 | :escape_pattern => /\{\{-([\s\S]+?)\}\}/ 10 | } 11 | 12 | QUESTION_MARK_SYNTAX = { 13 | :evaluation_pattern => /<\?([\s\S]+?)\?>/, 14 | :interpolation_pattern => /<\?=([\s\S]+?)\?>/, 15 | :escape_pattern => /<\?-([\s\S]+?)\?>/ 16 | } 17 | 18 | module TestHelper 19 | def test(name, &block) 20 | define_method("test #{name.inspect}", &block) 21 | end 22 | end 23 | 24 | class EJSCompilationTest < Test::Unit::TestCase 25 | extend TestHelper 26 | 27 | test "compile" do 28 | result = EJS.compile("Hello <%= name %>") 29 | assert_match FUNCTION_PATTERN, result 30 | assert_no_match(/Hello \<%= name %\>/, result) 31 | end 32 | 33 | test "compile with custom syntax" do 34 | standard_result = EJS.compile("Hello <%= name %>") 35 | braced_result = EJS.compile("Hello {{= name }}", BRACE_SYNTAX) 36 | 37 | assert_match FUNCTION_PATTERN, braced_result 38 | assert_equal standard_result, braced_result 39 | end 40 | end 41 | 42 | class EJSCustomPatternTest < Test::Unit::TestCase 43 | extend TestHelper 44 | 45 | def setup 46 | @original_evaluation_pattern = EJS.evaluation_pattern 47 | @original_interpolation_pattern = EJS.interpolation_pattern 48 | EJS.evaluation_pattern = BRACE_SYNTAX[:evaluation_pattern] 49 | EJS.interpolation_pattern = BRACE_SYNTAX[:interpolation_pattern] 50 | end 51 | 52 | def teardown 53 | EJS.interpolation_pattern = @original_interpolation_pattern 54 | EJS.evaluation_pattern = @original_evaluation_pattern 55 | end 56 | 57 | test "compile" do 58 | result = EJS.compile("Hello {{= name }}") 59 | assert_match FUNCTION_PATTERN, result 60 | assert_no_match(/Hello \{\{= name \}\}/, result) 61 | end 62 | 63 | test "compile with custom syntax" do 64 | standard_result = EJS.compile("Hello {{= name }}") 65 | question_result = EJS.compile("Hello = name ?>", QUESTION_MARK_SYNTAX) 66 | 67 | assert_match FUNCTION_PATTERN, question_result 68 | assert_equal standard_result, question_result 69 | end 70 | end 71 | 72 | class EJSEvaluationTest < Test::Unit::TestCase 73 | extend TestHelper 74 | 75 | test "quotes" do 76 | template = "<%= thing %> is gettin' on my noives!" 77 | assert_equal "This is gettin' on my noives!", EJS.evaluate(template, :thing => "This") 78 | end 79 | 80 | test "backslashes" do 81 | template = "<%= thing %> is \\ridanculous" 82 | assert_equal "This is \\ridanculous", EJS.evaluate(template, :thing => "This") 83 | end 84 | 85 | test "backslashes into interpolation" do 86 | template = %q{<%= "Hello \"World\"" %>} 87 | assert_equal 'Hello "World"', EJS.evaluate(template) 88 | end 89 | 90 | test "implicit semicolon" do 91 | template = "<% var foo = 'bar' %>" 92 | assert_equal '', EJS.evaluate(template) 93 | end 94 | 95 | test "iteration" do 96 | template = "
Just some text. Hey, I know this is silly but it aids consistency.