aboutsummaryrefslogblamecommitdiffstats
path: root/actionpack/lib/action_view/template/resolver.rb
blob: 0b4c62d4d07028a0412c94bc0e471315a561ef1c (plain) (tree)
1
2
3
4
5
6
7
                  
                                       
 
                 

                       
                                




                                                                
                   

                                    
    



                                                                                   

       







                                                                    
    







                                                     
         
       
 
                    
                             
                                                                  

                
       
    


                                                               
         
       
 





                                              
 
                                     
 



                          




































                                                                                     
           
         
       
  



                                                             











                                                                              
           
      

                                                    
       
 












                                                                                 
         
       
     









                                                                          
  
require "pathname"
require "action_view/template/template"

module ActionView
  # Abstract superclass
  class Resolver
    def initialize(options = {})
      @cache  = options[:cache]
      @cached = {}
    end

    # Normalizes the arguments and passes it on to find_template
    def find(*args)
      find_all_by_parts(*args).first
    end
    
    def find_all_by_parts(name, details = {}, prefix = nil, partial = nil)
      details[:locales] = [I18n.locale]
      name = name.to_s.gsub(handler_matcher, '').split("/")
      find_templates(name.pop, details, [prefix, *name].compact.join("/"), partial)
    end
  
  private

    # This is what child classes implement. No defaults are needed
    # because Resolver guarantees that the arguments are present and
    # normalized.
    def find_templates(name, details, prefix, partial)
      raise NotImplementedError
    end
    
    def valid_handlers
      @valid_handlers ||= TemplateHandlers.extensions
    end

    def handler_matcher
      @handler_matcher ||= begin
        e = valid_handlers.join('|')
        /\.(?:#{e})$/
      end
    end

    def handler_glob
      @handler_glob ||= begin
        e = TemplateHandlers.extensions.map{|h| ".#{h}"}.join(",")
        "{#{e}}"
      end
    end
    
    def formats_glob
      @formats_glob ||= begin
        '{' + Mime::SET.symbols.map { |l| ".#{l}," }.join + '}'
      end
    end

    def cached(key)
      return yield unless @cache
      return @cached[key] if @cached.key?(key)
      @cached[key] = yield
    end
  end

  class FileSystemResolver < Resolver

    def self.cached_glob
      @@cached_glob ||= {}
    end

    def initialize(path, options = {})
      raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
      super(options)
      @path = Pathname.new(path).expand_path
    end

    # TODO: This is the currently needed API. Make this suck less
    # ==== <suck>
    attr_reader :path

    def to_s
      path.to_s
    end

    def to_str
      path.to_s
    end

    def ==(path)
      to_str == path.to_str
    end

    def eql?(path)
      to_str == path.to_str
    end
    # ==== </suck>

    def find_templates(name, details, prefix, partial, root = "#{@path}/")
      if glob = details_to_glob(name, details, prefix, partial, root)
        cached(glob) do
          Dir[glob].map do |path|
            next if File.directory?(path)
            source = File.read(path)
            identifier = Pathname.new(path).expand_path.to_s

            Template.new(source, identifier, *path_to_details(path))
          end.compact
        end
      end
    end
  
  private

    # :api: plugin
    def details_to_glob(name, details, prefix, partial, root)
      self.class.cached_glob[[name, prefix, partial, details, root]] ||= begin
        path = ""
        path << "#{prefix}/" unless prefix.empty?
        path << (partial ? "_#{name}" : name)

        extensions = ""
        [:locales, :formats].each do |k|
          extensions << if exts = details[k]
            '{' + exts.map {|e| ".#{e},"}.join + '}'
          else
            k == :formats ? formats_glob : ''
          end
        end
      
        "#{root}#{path}#{extensions}#{handler_glob}"
      end
    end

    # TODO: fix me
    # :api: plugin
    def path_to_details(path)
      # [:erb, :format => :html, :locale => :en, :partial => true/false]
      if m = path.match(%r'/(_)?[\w-]+(\.[\w-]+)*\.(\w+)$')
        partial = m[1] == '_'
        details = (m[2]||"").split('.').reject { |e| e.empty? }
        handler = Template.handler_class_for_extension(m[3])

        format  = Mime[details.last] && details.pop.to_sym
        locale  = details.last && details.pop.to_sym

        return handler, :format => format, :locale => locale, :partial => partial
      end
    end
  end

  class FileSystemResolverWithFallback < FileSystemResolver

    def find_templates(name, details, prefix, partial)
      templates = super
      return super(name, details, prefix, partial, '') if templates.empty?
      templates
    end

  end
end