resolver.rb 4.5 KB
Newer Older
1
require "pathname"
2
require "active_support/core_ext/class"
C
Carlhuda 已提交
3
require "action_view/template"
4

5
module ActionView
6 7
  # Abstract superclass
  class Resolver
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

    class_inheritable_accessor(:registered_details)
    self.registered_details = {}

    def self.register_detail(name, options = {})
      registered_details[name] = lambda do |val|
        val ||= yield
        val |= [nil] unless options[:allow_nil] == false
        val
      end
    end

    register_detail(:locale)  { [I18n.locale] }
    register_detail(:formats) { Mime::SET.symbols }
    register_detail(:handlers, :allow_nil => false) do
C
Carlhuda 已提交
23
      Template::Handlers.extensions
24 25
    end

26
    def initialize(options = {})
27 28 29 30 31
      @cache  = options[:cache]
      @cached = {}
    end

    # Normalizes the arguments and passes it on to find_template
32
    def find(*args)
33
      find_all(*args).first
34
    end
35 36 37 38 39 40 41 42

    def find_all(name, details = {}, prefix = nil, partial = nil)
      details = normalize_details(details)
      name, prefix = normalize_name(name, prefix)

      cached([name, details, prefix, partial]) do
        find_templates(name, details, prefix, partial)
      end
43
    end
44

45 46 47 48 49 50 51 52 53
  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

54 55
    def normalize_details(details)
      details = details.dup
56 57
      # TODO: Refactor this concern out of the resolver
      details.delete(:formats) if details[:formats] == [:"*/*"]
58 59
      registered_details.each do |k, v|
        details[k] = v.call(details[k])
60
      end
61
      details
62
    end
63

64 65 66 67
    # Support legacy foo.erb names even though we now ignore .erb
    # as well as incorrectly putting part of the path in the template
    # name instead of the prefix.
    def normalize_name(name, prefix)
C
Carlhuda 已提交
68
      handlers = Template::Handlers.extensions.join('|')
69 70 71 72
      name = name.to_s.gsub(/\.(?:#{handlers})$/, '')

      parts = name.split('/')
      return parts.pop, [prefix, *parts].compact.join("/")
73
    end
74

75 76 77 78 79 80
    def cached(key)
      return yield unless @cache
      return @cached[key] if @cached.key?(key)
      @cached[key] = yield
    end
  end
81

82
  class PathResolver < Resolver
83

84
    EXTENSION_ORDER = [:locale, :formats, :handlers]
85 86

    def to_s
87
      @path.to_s
88
    end
89
    alias to_path to_s
90

91 92 93
    def find_templates(name, details, prefix, partial)
      path = build_path(name, details, prefix, partial)
      query(path, EXTENSION_ORDER.map { |ext| details[ext] })
94
    end
95

96 97
  private

98 99 100 101 102 103 104 105 106 107 108
    def build_path(name, details, prefix, partial)
      path = ""
      path << "#{prefix}/" unless prefix.empty?
      path << (partial ? "_#{name}" : name)
      path
    end

    def query(path, exts)
      query = "#{@path}/#{path}"
      exts.each do |ext|
        query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}'
109
      end
110

111 112 113
      Dir[query].reject { |p| File.directory?(p) }.map do |p|
        Template.new(File.read(p), File.expand_path(p), *path_to_details(p))
      end
114
    end
115

116 117
    # # TODO: fix me
    # # :api: plugin
118 119
    def path_to_details(path)
      # [:erb, :format => :html, :locale => :en, :partial => true/false]
J
José Valim 已提交
120 121 122 123
      if m = path.match(%r'((^|.*/)(_)?[\w-]+)((?:\.[\w-]+)*)\.(\w+)$')
        partial = m[3] == '_'
        details = (m[4]||"").split('.').reject { |e| e.empty? }
        handler = Template.handler_class_for_extension(m[5])
124 125 126 127

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

J
José Valim 已提交
128 129 130 131
        virtual_path = (m[1].gsub("#{@path}/", "") << details.join("."))

        return handler, :format => format, :locale => locale, :partial => partial,
                        :virtual_path => virtual_path
132 133
      end
    end
134
  end
135

136 137 138 139 140 141 142 143
  class FileSystemResolver < PathResolver
    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
  end

144
  # TODO: remove hack
145 146 147 148 149
  class FileSystemResolverWithFallback < Resolver
    def initialize(path, options = {})
      super(options)
      @paths = [FileSystemResolver.new(path, options), FileSystemResolver.new("", options), FileSystemResolver.new("/", options)]
    end
150

151 152 153 154 155 156
    def find_templates(*args)
      @paths.each do |p|
        template = p.find_templates(*args)
        return template unless template.empty?
      end
      []
157 158
    end

159 160 161
    def to_s
      @paths.first.to_s
    end
162
  end
163
end