aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template/resolver.rb
blob: ebfc6cc8ce5c1bfa1b474989f765f8d3f56c49dc (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
146
147
148
149
150
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
      e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join
      "{#{e}}"
    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 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)
      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

    # 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