aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view/template_finder.rb
blob: f483d24a2f68b2ad173a8bf9fcdcdfd3a811f8da (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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
module ActionView #:nodoc:
  class TemplateFinder #:nodoc:

    class InvalidViewPath < StandardError #:nodoc:
      attr_reader :unprocessed_path
      def initialize(path)
        @unprocessed_path = path
        super("Unprocessed view path found: #{@unprocessed_path.inspect}.  Set your view paths with #append_view_path, #prepend_view_path, or #view_paths=.")
      end
    end

    cattr_reader :processed_view_paths
    @@processed_view_paths = Hash.new {|hash, key| hash[key] = []}

    cattr_reader :file_extension_cache
    @@file_extension_cache = Hash.new {|hash, key|
      hash[key] = Hash.new {|hash, key| hash[key] = []}
    }

    class << self #:nodoc:

      # This method is not thread safe. Mutex should be used whenever this is accessed from an instance method
      def process_view_paths(*view_paths)
        view_paths.flatten.compact.each do |dir|
          next if @@processed_view_paths.has_key?(dir)

          @@processed_view_paths[dir] = []
          Dir.glob("#{dir}/**/*").each do |file|
            unless File.directory?(file)
              @@processed_view_paths[dir] << file.split(dir).last.sub(/^\//, '')

              # Build extension cache
              extension = file.split(".").last
              if template_handler_extensions.include?(extension)
                key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '')
                @@file_extension_cache[dir][key] << extension
              end
            end
          end
        end
      end

      def update_extension_cache_for(extension)
        @@processed_view_paths.keys.each do |dir|
          Dir.glob("#{dir}/**/*.#{extension}").each do |file|
            key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '')
            @@file_extension_cache[dir][key] << extension
          end
        end
      end

      def template_handler_extensions
        ActionView::Template.template_handler_extensions
      end

      def reload!
        view_paths = @@processed_view_paths.keys

        @@processed_view_paths = Hash.new {|hash, key| hash[key] = []}
        @@file_extension_cache = Hash.new {|hash, key|
          hash[key] = Hash.new {|hash, key| hash[key] = []}
        }

        process_view_paths(view_paths)
      end
    end

    attr_accessor :view_paths

    def initialize(*args)
      @template = args.shift

      @view_paths = args.flatten
      @view_paths = @view_paths.respond_to?(:find) ? @view_paths.dup : [*@view_paths].compact
      check_view_paths(@view_paths)
    end

    def prepend_view_path(path)
      @view_paths.unshift(*path)

      self.class.process_view_paths(path)
    end

    def append_view_path(path)
      @view_paths.push(*path)

      self.class.process_view_paths(path)
    end

    def view_paths=(path)
      @view_paths = path
      self.class.process_view_paths(path)
    end

    def pick_template(template_path, extension)
      file_name = "#{template_path}.#{extension}"
      base_path = find_base_path_for(file_name)
      base_path.blank? ? false : "#{base_path}/#{file_name}"
    end
    alias_method :template_exists?, :pick_template

    def file_exists?(template_path)
      # Clear the forward slash in the beginning if exists
      template_path = template_path.sub(/^\//, '')

      template_file_name, template_file_extension = path_and_extension(template_path)

      if template_file_extension
        template_exists?(template_file_name, template_file_extension)
      else
        template_exists?(template_file_name, pick_template_extension(template_path))
      end
    end

    def find_base_path_for(template_file_name)
      @view_paths.find { |path| @@processed_view_paths[path].include?(template_file_name) }
    end

    # Returns the view path that the full path resides in.
    def extract_base_path_from(full_path)
      @view_paths.find { |p| full_path[0..p.size - 1] == p }
    end

    # Gets the extension for an existing template with the given template_path.
    # Returns the format with the extension if that template exists.
    #
    #   pick_template_extension('users/show')
    #   # => 'html.erb'
    #
    #   pick_template_extension('users/legacy')
    #   # => "rhtml"
    #
    def pick_template_extension(template_path)
      find_template_extension_from_handler(template_path) || find_template_extension_from_first_render
    end

    def find_template_extension_from_handler(template_path)
      formatted_template_path = "#{template_path}.#{@template.template_format}"

      view_paths.each do |path|
        if (extensions = @@file_extension_cache[path][formatted_template_path]).any?
          return "#{@template.template_format}.#{extensions.first}"
        elsif (extensions = @@file_extension_cache[path][template_path]).any?
          return extensions.first.to_s
        end
      end
      nil
    end

    # Splits the path and extension from the given template_path and returns as an array.
    def path_and_extension(template_path)
      template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
      [ template_path_without_extension, $1 ]
    end

    # Determine the template extension from the <tt>@first_render</tt> filename
    def find_template_extension_from_first_render
      File.basename(@template.first_render.to_s)[/^[^.]+\.(.+)$/, 1]
    end

    private

    def check_view_paths(view_paths)
      view_paths.each do |path|
        raise InvalidViewPath.new(path) unless @@processed_view_paths.has_key?(path)
      end
    end

  end
end