module EJS

Constants

ASSET_DIR
DEFAULTS

Public Class Methods

compile(source, options = {}) click to toggle source
# File lib/ejs.rb, line 54
def compile(source, options = {})
  options = default(options)
  
  output = "function(locals, escape) {\n"
  output << function_source(source, options)[0]
  output << "}"
  output
end
evaluate(template, locals = {}, options = {}) click to toggle source

Evaluates an EJS template with the given local variables and compiler options. You will need the ExecJS (github.com/sstephenson/execjs/) library and a JavaScript runtime available.

EJS.evaluate("Hello <%= name %>", name: "world")
# => "Hello world"
# File lib/ejs.rb, line 72
    def evaluate(template, locals = {}, options = {})
      require "execjs"
      context = ExecJS.compile(<<-JS)
        #{escape_function}
        
        var template = #{compile(template, options)}
        var evaluate = function(locals) {
          return template(locals, escape);
        }
      JS
      context.call("evaluate", locals)
    end
transform(source, options = {}) click to toggle source

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){...}"
# File lib/ejs.rb, line 36
def transform(source, options = {})
  options = default(options)
  
  output = if options[:escape]
    "import {" + options[:escape].split('.').reverse.join(" as escape} from '") + "';\n"
  else
    "import {escape} from 'ejs';\n"
  end
  
  fs = function_source(source, options)
  output << fs[1]
  output << "export default function (locals) {\n"
  output << fs[0]
  output << "}"

  output
end

Protected Class Methods

chars_balanced?(str, chars) click to toggle source
# File lib/ejs.rb, line 118
def chars_balanced?(str, chars)
  a = chars[0]
  b = chars[1]
  str = str.sub(/"(\\.|[^"])+"/, '')
  str = str.sub(/'(\\.|[^'])+'/, '')
  a_count = str.scan(/#{a}/).length
  b_count = str.scan(/#{b}/).length

  a_count - b_count
end
default(options) click to toggle source
# File lib/ejs.rb, line 87
def default(options)
  options = DEFAULTS.merge(options)

  [:open_tag_modifiers, :close_tag_modifiers].each do |k|
    DEFAULTS[k].each do |sk, v|
      next if options[k].has_key?(sk)
      options[k] = v
    end
  end

  options
end
digest(source, options) { |source[0...(matching_modifier, :js, last_tag_modifier| ... } click to toggle source
# File lib/ejs.rb, line 130
def digest(source, options)
  open_tag_count = 0
  close_tag_count = 0
  tag_length = nil
  # var index, tagType, tagModifier, tagModifiers, matchingModifier, prefix;
  index = nil
  tag_type = nil
  tag_modifiers = nil
  tag_modifier = nil
  prefix =nil
  matching_modifier = nil
  last_tag_modifier = nil
  next_open_index = source.index(options[:open_tag])
  next_close_index = source.index(options[:close_tag])
  
  while next_open_index || next_close_index
    if (next_close_index && (!next_open_index || next_close_index < next_open_index))
      index = next_close_index
      tag_type = :close
      tag_length = options[:close_tag].length
      tag_modifiers = options[:close_tag_modifiers]
      close_tag_count += 1
      matching_modifier = tag_modifiers.find do |k, v|
        source[index - v.length, v.length] == v
      end
    else
      index = next_open_index
      tag_type = :open
      tag_length = options[:open_tag].length
      tag_modifiers = options[:open_tag_modifiers]
      open_tag_count += 1
      matching_modifier = tag_modifiers.find do |k, v|
        source[index + tag_length, v.length] == v
      end
    end

    if matching_modifier
      tag_length += matching_modifier[1].length
      tag_modifier = matching_modifier[0]
    else
      tag_modifier = :default
    end

    if tag_modifier == :literal
      if tag_type == :open
        source = source[0, tag_length - matching_modifier[1].length] + source[(index + tag_length)..-1]
        # source = source.slice(0, index + tagLength - matchingModifier[1].length) + source.slice(index + tagLength);
        open_tag_count -= 1
      else
        close_tag_count -= 1
        if index == 0
          source = source[(index + matching_modifier[1].length)..-1]
        else
          source = source[0..index] + source[(index + matching_modifier[1].length)..-1]
        end
      end

      next_open_index = source.index(options.openTag, index + tag_length - matching_modifier[1].length);
      next_close_index = source.index(options.closeTag, index + tag_length - matching_modifier[1].length);
      next
    end
    
    if index != 0
      if tag_type == :close
        if matching_modifier
          yield(source[0...(matching_modifier[1].length)], :js, last_tag_modifier)
        else
          yield(source[0...index], :js, last_tag_modifier)
        end
      else
        yield(source[0...index], :text, last_tag_modifier)
      end

      source = source[index..-1]
    end

    if tag_type == :close && matching_modifier
      source = source[(tag_length - matching_modifier[1].length)..-1]
      source.lstrip!
    else
      source = source[tag_length..-1]
    end
    next_open_index = source.index(options[:open_tag])
    next_close_index = source.index(options[:close_tag])
    last_tag_modifier = tag_modifier
  end

  if open_tag_count != close_tag_count
    raise "Could not find closing tag for \"#{options[(tag_type.to_s + '_tag').to_sym]}\"."
  end
  
  yield(source, :text, tag_modifier)
end
escape_function(name='escape') click to toggle source
# File lib/ejs.rb, line 104
      def escape_function(name='escape')
        <<-JS
          function #{name}(string) {
            if (string !== undefined && string != null) {
              return String(string).replace(/[&<>'"\\/]/g, function (c) {
                return '&#' + c.codePointAt(0) + ';';
              });
            } else {
              return '';
            }
          }
        JS
      end
escape_module() click to toggle source
# File lib/ejs.rb, line 100
def escape_module
  escape_function.sub('function', 'export function')
end
function_source(source, options) click to toggle source
# File lib/ejs.rb, line 225
def function_source(source, options)
  stack = []
  imports = ""
  output =  "    var __output = [], __append = __output.push.bind(__output);\n"
  output << "    with (locals || {}) {\n" unless options[:strict]

  digest(source, options) do |segment, type, modifier|
    if type == :js
      if segment.match(/\A\s*\}.*\{\s*\Z/m)
        output << "        " << segment << "\n"
      elsif segment.match(/\A\s*\}/m)
        case stack.pop
        when :escape
          output << "\n            return __output.join(\"\");\n"
          output << segment << "        ));\n"
        when :unescape
          output << "\n            return __output.join(\"\");\n"
          output << segment << "        );\n"
        else
          output << "        " << segment << "\n"
        end
      elsif segment.match(/\)\s*\{\s*\Z/m)
        stack << modifier
        case modifier
        when :escape
          output << "        __append(escape(" << segment
          output << "\n            var __output = [], __append = __output.push.bind(__output);\n"
        when :unescape
          output << "        __append(" << segment
          output << "\n            var __output = [], __append = __output.push.bind(__output);\n"
        else
          output << "        " << segment << "\n"
        end
      else
        case modifier
        when :escape
          output << "        __append(escape(" << segment << "));\n"
        when :unescape
          output << "        __append(" << segment << ");\n"
        else
          if segment =~ /\A\s*import/
            imports << segment.strip
            imports << ';' unless segment =~ /;\s*\Z/
            imports << "\n"
          else
            output << "        " << segment << "\n"
          end
        end
      end
    elsif segment.length > 0
      output << '        __append("' + segment.gsub("\\"){"\\\\"}.gsub(/\n/, '\\n').gsub(/\r/, '\\r').gsub('"', '\\"') + "\");\n"
    end
  end

  output << "    }\n" unless options[:strict]
  output << "    return __output.join(\"\");\n"
  imports << "\n"
  
  [output, imports]
end