Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / actionpack / lib / action_view / template.rb
1 module ActionView #:nodoc:
2 class Template
3 class Path
4 attr_reader :path, :paths
5 delegate :hash, :inspect, :to => :path
6
7 def initialize(path)
8 raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
9 @path = (path.ends_with?(File::SEPARATOR) ? path.to(-2) : path).freeze
10 end
11
12 def to_s
13 if defined?(RAILS_ROOT)
14 path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
15 else
16 path.to_s
17 end
18 end
19
20 def to_str
21 path.to_str
22 end
23
24 def ==(path)
25 to_str == path.to_str
26 end
27
28 def eql?(path)
29 to_str == path.to_str
30 end
31
32 # Returns a ActionView::Template object for the given path string. The
33 # input path should be relative to the view path directory,
34 # +hello/index.html.erb+. This method also has a special exception to
35 # match partial file names without a handler extension. So
36 # +hello/index.html+ will match the first template it finds with a
37 # known template extension, +hello/index.html.erb+. Template extensions
38 # should not be confused with format extensions +html+, +js+, +xml+,
39 # etc. A format must be supplied to match a formated file. +hello/index+
40 # will never match +hello/index.html.erb+.
41 def [](path)
42 end
43
44 def load!
45 end
46
47 def self.new_and_loaded(path)
48 returning new(path) do |path|
49 path.load!
50 end
51 end
52
53 private
54 def relative_path_for_template_file(full_file_path)
55 full_file_path.split("#{@path}/").last
56 end
57 end
58
59 class EagerPath < Path
60 def load!
61 return if @loaded
62
63 @paths = {}
64 templates_in_path do |template|
65 template.load!
66 template.accessible_paths.each do |path|
67 @paths[path] = template
68 end
69 end
70 @paths.freeze
71 @loaded = true
72 end
73
74 def [](path)
75 load! unless @loaded
76 @paths[path]
77 end
78
79 private
80 def templates_in_path
81 (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
82 yield create_template(file) unless File.directory?(file)
83 end
84 end
85
86 def create_template(file)
87 Template.new(relative_path_for_template_file(file), self)
88 end
89 end
90
91 extend TemplateHandlers
92 extend ActiveSupport::Memoizable
93 include Renderable
94
95 # Templates that are exempt from layouts
96 @@exempt_from_layout = Set.new([/\.rjs$/])
97
98 # Don't render layouts for templates with the given extensions.
99 def self.exempt_from_layout(*extensions)
100 regexps = extensions.collect do |extension|
101 extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
102 end
103 @@exempt_from_layout.merge(regexps)
104 end
105
106 attr_accessor :template_path, :filename, :load_path, :base_path
107 attr_accessor :locale, :name, :format, :extension
108 delegate :to_s, :to => :path
109
110 def initialize(template_path, load_path)
111 @template_path = template_path.dup
112 @load_path, @filename = load_path, File.join(load_path, template_path)
113 @base_path, @name, @locale, @format, @extension = split(template_path)
114 @base_path.to_s.gsub!(/\/$/, '') # Push to split method
115
116 # Extend with partial super powers
117 extend RenderablePartial if @name =~ /^_/
118 end
119
120 def accessible_paths
121 paths = []
122
123 if valid_extension?(extension)
124 paths << path
125 paths << path_without_extension
126 if multipart?
127 formats = format.split(".")
128 paths << "#{path_without_format_and_extension}.#{formats.first}"
129 paths << "#{path_without_format_and_extension}.#{formats.second}"
130 end
131 else
132 # template without explicit template handler should only be reachable through its exact path
133 paths << template_path
134 end
135
136 paths
137 end
138
139 def format_and_extension
140 (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
141 end
142 memoize :format_and_extension
143
144 def multipart?
145 format && format.include?('.')
146 end
147
148 def content_type
149 format.gsub('.', '/')
150 end
151
152 def mime_type
153 Mime::Type.lookup_by_extension(format) if format && defined?(::Mime)
154 end
155 memoize :mime_type
156
157 def path
158 [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
159 end
160 memoize :path
161
162 def path_without_extension
163 [base_path, [name, locale, format].compact.join('.')].compact.join('/')
164 end
165 memoize :path_without_extension
166
167 def path_without_format_and_extension
168 [base_path, [name, locale].compact.join('.')].compact.join('/')
169 end
170 memoize :path_without_format_and_extension
171
172 def relative_path
173 path = File.expand_path(filename)
174 path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT)
175 path
176 end
177 memoize :relative_path
178
179 def exempt_from_layout?
180 @@exempt_from_layout.any? { |exempted| path =~ exempted }
181 end
182
183 def source
184 File.read(filename)
185 end
186 memoize :source
187
188 def method_segment
189 relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
190 end
191 memoize :method_segment
192
193 def render_template(view, local_assigns = {})
194 render(view, local_assigns)
195 rescue Exception => e
196 raise e unless filename
197 if TemplateError === e
198 e.sub_template_of(self)
199 raise e
200 else
201 raise TemplateError.new(self, view.assigns, e)
202 end
203 end
204
205 def load!
206 freeze
207 end
208
209 private
210 def valid_extension?(extension)
211 !Template.registered_template_handler(extension).nil?
212 end
213
214 def valid_locale?(locale)
215 I18n.available_locales.include?(locale.to_sym)
216 end
217
218 # Returns file split into an array
219 # [base_path, name, locale, format, extension]
220 def split(file)
221 if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/)
222 base_path = m[1]
223 name = m[2]
224 extensions = m[3]
225 else
226 return
227 end
228
229 locale = nil
230 format = nil
231 extension = nil
232
233 if m = extensions.split(".")
234 if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three
235 locale = m[0]
236 format = m[1]
237 extension = m[2]
238 elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats
239 format = "#{m[0]}.#{m[1]}"
240 extension = m[2]
241 elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension
242 locale = m[0]
243 extension = m[1]
244 elsif valid_extension?(m[1]) # format and extension
245 format = m[0]
246 extension = m[1]
247 elsif valid_extension?(m[0]) # Just extension
248 extension = m[0]
249 else # No extension
250 format = m[0]
251 end
252 end
253
254 [base_path, name, locale, format, extension]
255 end
256 end
257 end