lib/opal/erb.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

require 'opal/compiler'

module Opal
  module ERB
    # Compile ERB code into javascript.
    #
    # [Opal::ERB] can be used to compile [ERB] templates into javascript code.
    # This module uses the [Opal::Compiler] internally.
    #
    # Compiled templates, when run in a javascript environment, will appear
    # under the `Template` namespace, and can be accessed as:
    #
    #     Template['template_name'] # => template instance
    #
    # @example
    #
    #     source = "<div><%= @content %></div>"
    #
    #     Opal::ERB.compile source, "my_template.erb"
    #
    # @param source [String] erb content
    # @param file_name [String] filename for reference in template
    # @return [String] javascript code
    #
    def self.compile(source, file_name = '(erb)')
      Compiler.new(source, file_name).compile
    end

    class Compiler
      BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/.freeze

      def initialize(source, file_name = '(erb)')
        @source, @file_name, @result = source, file_name, source
      end

      def prepared_source
        @prepared_source ||= begin
          source = @source
          source = fix_quotes(source)
          source = find_contents(source)
          source = find_code(source)
          source = wrap_compiled(source)
          source = require_erb(source)
          source
        end
      end

      def compile
        Opal.compile prepared_source
      end

      def fix_quotes(result)
        result.gsub '"', '\\"'
      end

      def require_erb(result)
        'require "erb";' + result
      end

      def find_contents(result)
        result.gsub(/<%=([\s\S]+?)%>/) do
          inner = Regexp.last_match(1).gsub(/\\'/, "'").gsub(/\\"/, '"')

          if inner =~ BLOCK_EXPR
            "\")\noutput_buffer.append= #{inner}\noutput_buffer.append(\""
          else
            "\")\noutput_buffer.append=(#{inner})\noutput_buffer.append(\""
          end
        end
      end

      def find_code(result)
        result.gsub(/<%([\s\S]+?)%>/) do
          inner = Regexp.last_match(1).gsub(/\\"/, '"')
          "\")\n#{inner}\noutput_buffer.append(\""
        end
      end

      def wrap_compiled(result)
        path = @file_name.sub(/\.opalerb#{REGEXP_END}/, '')
        "Template.new('#{path}') do |output_buffer|\noutput_buffer.append(\"#{result}\")\noutput_buffer.join\nend\n"
      end
    end
  end
end