aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template/resolver.rb
blob: 4442db22f6b872a000ddd8f00dddcd75bf28a220 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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

    def to_s
      @path.to_s
    end
    alias to_path to_s

    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|
          # TODO: OMG NO
          if details[k] == [:"*/*"]
            extensions << formats_glob if k == :formats
          elsif exts = details[k]
            extensions << '{' + exts.map {|e| ".#{e},"}.join + '}'
          else
            extensions << formats_glob if k == :formats
          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