lib/opal/sprockets/source_map_server.rb



require 'cgi'

module Opal
  class SourceMapServer
    # Carelessly taken from Sprockets::Caching (Sprockets v2)
    class Cache
      def initialize
        @cache = {}
      end

      attr_reader :cache

      def cache_get(key)
        # `Cache#get(key)` for Memcache
        if cache.respond_to?(:get)
          cache.get(key)

        # `Cache#[key]` so `Hash` can be used
        elsif cache.respond_to?(:[])
          cache[key]

        # `Cache#read(key)` for `ActiveSupport::Cache` support
        elsif cache.respond_to?(:read)
          cache.read(key)

        else
          nil
        end
      end

      def cache_set(key, value)
        # `Cache#set(key, value)` for Memcache
        if cache.respond_to?(:set)
          cache.set(key, value)

        # `Cache#[key]=value` so `Hash` can be used
        elsif cache.respond_to?(:[]=)
          cache[key] = value

        # `Cache#write(key, value)` for `ActiveSupport::Cache` support
        elsif cache.respond_to?(:write)
          cache.write(key, value)
        end

        value
      end
    end

    def self.get_map_cache(sprockets, logical_path)
      logical_path = logical_path.gsub(/\.js#{REGEXP_END}/, '')
      cache_key = cache_key_for_path(logical_path+'.map')
      cache(sprockets).cache_get(cache_key)
    end

    def self.set_map_cache(sprockets, logical_path, map_contents)
      logical_path = logical_path.gsub(/\.js#{REGEXP_END}/, '')
      cache_key = cache_key_for_path(logical_path+'.map')
      cache(sprockets).cache_set(cache_key, map_contents)
    end

    def self.cache(sprockets)
      @cache ||= sprockets.cache ? sprockets : Cache.new
    end

    def self.cache_key_for_path(logical_path)
      base_name = logical_path.gsub(/\.js#{REGEXP_END}/, '')
      File.join('opal', 'source_maps', base_name)
    end


    def initialize sprockets, prefix = '/'
      @sprockets = sprockets
      @prefix = prefix
    end

    attr_reader :sprockets, :prefix

    def inspect
      "#<#{self.class}:#{object_id}>"
    end

    def call(env)
      prefix_regex = %r{^(?:#{prefix}/|/)}
      path_info = CGI.unescape(env['PATH_INFO'].to_s).sub(prefix_regex, '')

      case path_info
      when %r{^(.*)\.self\.map$}
        path = $1
        asset  = sprockets[path+'.js']
        return not_found(path) if asset.nil?

        # "logical_name" of a BundledAsset keeps the .js extension
        source = SourceMapServer.get_map_cache(sprockets, asset.logical_path)
        return not_found(asset) if source.nil?

        map = JSON.parse(source)
        map['sources'] = map['sources'].map {|s| "#{prefix}/#{s}"}
        source = map.to_json

        return [200, {"Content-Type" => "text/json"}, [source.to_s]]
      when %r{^(.*)\.rb$}
        begin
          asset = sprockets.resolve(path_info.sub(/\.rb#{REGEXP_END}/,''))
        rescue Sprockets::FileNotFound
          return not_found(path_info)
        end
        return [200, {"Content-Type" => "text/ruby"}, [Pathname(asset).read]]
      else
        not_found(path_info)
      end
    end

    def not_found(*messages)
      not_found = [404, {}, [{not_found: messages}.inspect]]
    end
  end
end