<commit_before>require 'execjs'
require 'json'

module Barber
  class Precompiler
    class << self
      def compile(template)
        instance.compile(template)
      end

      def compiler_version
        instance.compiler_version
      end

      private

      def instance
        @instance ||= new
      end
    end

    def compile(template)
      context.call precompile_function, sanitize(template)
    rescue ExecJS::ProgramError => ex
      raise Barber::PrecompilerError.new(template, ex)
    end

    def precompile_function
      "Barber.precompile"
    end

    def sources
      sources = [precompiler]
      sources << handlebars if handlebars_available?
      sources
    end

    def handlebars
      @handlebars ||= File.new(Handlebars::Source.bundled_path)
    end

    def compiler_version
      "handlebars:#{handlebars_version}"
    end

    private
    # This method handles different types of user input. The precompiler
    # can be called from many different places which create interesting
    # conditions.
    # Case 1: Rake-Pipeline-Web-Filters: calls with a JSON encoded string.
    # Case 2: Sanitize using a RegExp
    # Case 3: "Normal" input. Reading from a file or something like that.
    #
    # Each one of these cases is covered by a test case. If you find another breaking
    # use case please address it here with a regression test.
    def sanitize(template)
      begin
        if template =~ /\A".+"\Z/m
          # Case 1
          sanitize_with_json(template)
        else
          # Case 2
          sanitize_with_regexp(template)
        end
      rescue JSON::ParserError
        # Case 3
        template
      end
    end

    def sanitize_with_json(template)
      JSON.load(%Q|{"template":#{template}}|)['template']
    end

    def sanitize_with_regexp(template)
      if template.respond_to? :gsub
        sanitized = template.gsub(/\\n/,"\n")
        sanitized = sanitized.gsub(/\\["']/, '"')
        sanitized
      else
        template
      end
    end

    def precompiler
      @precompiler ||= File.new(File.expand_path("../javascripts/handlebars_precompiler.js", __FILE__))
    end

    def context
      @context ||= ExecJS.compile source
    end

    def source
      # hbs 3 has no reference to `window`.
      <<-SOURCE
if (typeof window === 'undefined') {
  window = this;
};
#{sources.map(&:read).join("\n;\n")}
      SOURCE
    end

    def handlebars_available?
      require "handlebars/source" # handlebars precompilation is optional feature.
    rescue LoadError => e
      raise e unless ['cannot load such file -- handlebars/source', 'no such file to load -- handlebars/source'].include?(e.message)
    ensure
      return !!defined?(Handlebars::Source)
    end

    def handlebars_version
      @handlebars_version ||= context.eval('Handlebars.VERSION')
    end
  end
end<commit_msg>Assign `self` if it is undefined<commit_after>require 'execjs'
require 'json'

module Barber
  class Precompiler
    class << self
      def compile(template)
        instance.compile(template)
      end

      def compiler_version
        instance.compiler_version
      end

      private

      def instance
        @instance ||= new
      end
    end

    def compile(template)
      context.call precompile_function, sanitize(template)
    rescue ExecJS::ProgramError => ex
      raise Barber::PrecompilerError.new(template, ex)
    end

    def precompile_function
      "Barber.precompile"
    end

    def sources
      sources = [precompiler]
      sources << handlebars if handlebars_available?
      sources
    end

    def handlebars
      @handlebars ||= File.new(Handlebars::Source.bundled_path)
    end

    def compiler_version
      "handlebars:#{handlebars_version}"
    end

    private
    # This method handles different types of user input. The precompiler
    # can be called from many different places which create interesting
    # conditions.
    # Case 1: Rake-Pipeline-Web-Filters: calls with a JSON encoded string.
    # Case 2: Sanitize using a RegExp
    # Case 3: "Normal" input. Reading from a file or something like that.
    #
    # Each one of these cases is covered by a test case. If you find another breaking
    # use case please address it here with a regression test.
    def sanitize(template)
      begin
        if template =~ /\A".+"\Z/m
          # Case 1
          sanitize_with_json(template)
        else
          # Case 2
          sanitize_with_regexp(template)
        end
      rescue JSON::ParserError
        # Case 3
        template
      end
    end

    def sanitize_with_json(template)
      JSON.load(%Q|{"template":#{template}}|)['template']
    end

    def sanitize_with_regexp(template)
      if template.respond_to? :gsub
        sanitized = template.gsub(/\\n/,"\n")
        sanitized = sanitized.gsub(/\\["']/, '"')
        sanitized
      else
        template
      end
    end

    def precompiler
      @precompiler ||= File.new(File.expand_path("../javascripts/handlebars_precompiler.js", __FILE__))
    end

    def context
      @context ||= ExecJS.compile source
    end

    def source
      # hbs 3 has no reference to `window` and `self`.
      <<-SOURCE
if (typeof window === 'undefined') {
  window = this;
};
if (typeof self === 'undefined') {
  self = this;
};
#{sources.map(&:read).join("\n;\n")}
      SOURCE
    end

    def handlebars_available?
      require "handlebars/source" # handlebars precompilation is optional feature.
    rescue LoadError => e
      raise e unless ['cannot load such file -- handlebars/source', 'no such file to load -- handlebars/source'].include?(e.message)
    ensure
      return !!defined?(Handlebars::Source)
    end

    def handlebars_version
      @handlebars_version ||= context.eval('Handlebars.VERSION')
    end
  end
end
{'fraction_non_alphanumeric': 0.09211204674342671, 'fraction_numerical': 0.002405911668671593, 'mean_word_length': 2.4035087719298245, 'pattern_counts': {'":': 2, '<': 15, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1520004', 'n_tokens_mistral': 1690, 'n_tokens_neox': 1647, 'n_words': 498}