actionview 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +274 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +34 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +205 -0
  7. data/lib/action_view/buffers.rb +49 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +93 -0
  10. data/lib/action_view/digestor.rb +116 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/helpers.rb +64 -0
  13. data/lib/action_view/helpers/active_model_helper.rb +49 -0
  14. data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
  15. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  16. data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
  17. data/lib/action_view/helpers/cache_helper.rb +200 -0
  18. data/lib/action_view/helpers/capture_helper.rb +216 -0
  19. data/lib/action_view/helpers/controller_helper.rb +25 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +30 -0
  21. data/lib/action_view/helpers/date_helper.rb +1075 -0
  22. data/lib/action_view/helpers/debug_helper.rb +39 -0
  23. data/lib/action_view/helpers/form_helper.rb +1876 -0
  24. data/lib/action_view/helpers/form_options_helper.rb +843 -0
  25. data/lib/action_view/helpers/form_tag_helper.rb +746 -0
  26. data/lib/action_view/helpers/javascript_helper.rb +75 -0
  27. data/lib/action_view/helpers/number_helper.rb +425 -0
  28. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  29. data/lib/action_view/helpers/record_tag_helper.rb +108 -0
  30. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  31. data/lib/action_view/helpers/sanitize_helper.rb +256 -0
  32. data/lib/action_view/helpers/tag_helper.rb +176 -0
  33. data/lib/action_view/helpers/tags.rb +41 -0
  34. data/lib/action_view/helpers/tags/base.rb +148 -0
  35. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  36. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  37. data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
  38. data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  41. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  42. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  43. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  44. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  45. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  46. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  47. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  48. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  50. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  51. data/lib/action_view/helpers/tags/label.rb +65 -0
  52. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  53. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  54. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  56. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  58. data/lib/action_view/helpers/tags/select.rb +41 -0
  59. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  61. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  62. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  65. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  66. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  67. data/lib/action_view/helpers/text_helper.rb +447 -0
  68. data/lib/action_view/helpers/translation_helper.rb +111 -0
  69. data/lib/action_view/helpers/url_helper.rb +625 -0
  70. data/lib/action_view/layouts.rb +426 -0
  71. data/lib/action_view/locale/en.yml +56 -0
  72. data/lib/action_view/log_subscriber.rb +44 -0
  73. data/lib/action_view/lookup_context.rb +249 -0
  74. data/lib/action_view/model_naming.rb +12 -0
  75. data/lib/action_view/path_set.rb +77 -0
  76. data/lib/action_view/railtie.rb +49 -0
  77. data/lib/action_view/record_identifier.rb +84 -0
  78. data/lib/action_view/renderer/abstract_renderer.rb +47 -0
  79. data/lib/action_view/renderer/partial_renderer.rb +492 -0
  80. data/lib/action_view/renderer/renderer.rb +50 -0
  81. data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
  82. data/lib/action_view/renderer/template_renderer.rb +96 -0
  83. data/lib/action_view/rendering.rb +145 -0
  84. data/lib/action_view/routing_url_for.rb +109 -0
  85. data/lib/action_view/tasks/dependencies.rake +17 -0
  86. data/lib/action_view/template.rb +340 -0
  87. data/lib/action_view/template/error.rb +141 -0
  88. data/lib/action_view/template/handlers.rb +53 -0
  89. data/lib/action_view/template/handlers/builder.rb +26 -0
  90. data/lib/action_view/template/handlers/erb.rb +145 -0
  91. data/lib/action_view/template/handlers/raw.rb +11 -0
  92. data/lib/action_view/template/resolver.rb +329 -0
  93. data/lib/action_view/template/text.rb +34 -0
  94. data/lib/action_view/template/types.rb +57 -0
  95. data/lib/action_view/test_case.rb +272 -0
  96. data/lib/action_view/testing/resolvers.rb +50 -0
  97. data/lib/action_view/vendor/html-scanner.rb +20 -0
  98. data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
  99. data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
  100. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
  101. data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
  102. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
  103. data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
  104. data/lib/action_view/version.rb +11 -0
  105. data/lib/action_view/view_paths.rb +96 -0
  106. metadata +218 -0
@@ -0,0 +1,17 @@
1
+ namespace :cache_digests do
2
+ desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
3
+ task :nested_dependencies => :environment do
4
+ abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
5
+ template, format = ENV['TEMPLATE'].split(".")
6
+ format ||= :html
7
+ puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).nested_dependencies
8
+ end
9
+
10
+ desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
11
+ task :dependencies => :environment do
12
+ abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
13
+ template, format = ENV['TEMPLATE'].split(".")
14
+ format ||= :html
15
+ puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).dependencies
16
+ end
17
+ end
@@ -0,0 +1,340 @@
1
+ require 'active_support/core_ext/object/try'
2
+ require 'active_support/core_ext/kernel/singleton_class'
3
+ require 'thread'
4
+
5
+ module ActionView
6
+ # = Action View Template
7
+ class Template
8
+ extend ActiveSupport::Autoload
9
+
10
+ # === Encodings in ActionView::Template
11
+ #
12
+ # ActionView::Template is one of a few sources of potential
13
+ # encoding issues in Rails. This is because the source for
14
+ # templates are usually read from disk, and Ruby (like most
15
+ # encoding-aware programming languages) assumes that the
16
+ # String retrieved through File IO is encoded in the
17
+ # <tt>default_external</tt> encoding. In Rails, the default
18
+ # <tt>default_external</tt> encoding is UTF-8.
19
+ #
20
+ # As a result, if a user saves their template as ISO-8859-1
21
+ # (for instance, using a non-Unicode-aware text editor),
22
+ # and uses characters outside of the ASCII range, their
23
+ # users will see diamonds with question marks in them in
24
+ # the browser.
25
+ #
26
+ # For the rest of this documentation, when we say "UTF-8",
27
+ # we mean "UTF-8 or whatever the default_internal encoding
28
+ # is set to". By default, it will be UTF-8.
29
+ #
30
+ # To mitigate this problem, we use a few strategies:
31
+ # 1. If the source is not valid UTF-8, we raise an exception
32
+ # when the template is compiled to alert the user
33
+ # to the problem.
34
+ # 2. The user can specify the encoding using Ruby-style
35
+ # encoding comments in any template engine. If such
36
+ # a comment is supplied, Rails will apply that encoding
37
+ # to the resulting compiled source returned by the
38
+ # template handler.
39
+ # 3. In all cases, we transcode the resulting String to
40
+ # the UTF-8.
41
+ #
42
+ # This means that other parts of Rails can always assume
43
+ # that templates are encoded in UTF-8, even if the original
44
+ # source of the template was not UTF-8.
45
+ #
46
+ # From a user's perspective, the easiest thing to do is
47
+ # to save your templates as UTF-8. If you do this, you
48
+ # do not need to do anything else for things to "just work".
49
+ #
50
+ # === Instructions for template handlers
51
+ #
52
+ # The easiest thing for you to do is to simply ignore
53
+ # encodings. Rails will hand you the template source
54
+ # as the default_internal (generally UTF-8), raising
55
+ # an exception for the user before sending the template
56
+ # to you if it could not determine the original encoding.
57
+ #
58
+ # For the greatest simplicity, you can support only
59
+ # UTF-8 as the <tt>default_internal</tt>. This means
60
+ # that from the perspective of your handler, the
61
+ # entire pipeline is just UTF-8.
62
+ #
63
+ # === Advanced: Handlers with alternate metadata sources
64
+ #
65
+ # If you want to provide an alternate mechanism for
66
+ # specifying encodings (like ERB does via <%# encoding: ... %>),
67
+ # you may indicate that you will handle encodings yourself
68
+ # by implementing <tt>self.handles_encoding?</tt>
69
+ # on your handler.
70
+ #
71
+ # If you do, Rails will not try to encode the String
72
+ # into the default_internal, passing you the unaltered
73
+ # bytes tagged with the assumed encoding (from
74
+ # default_external).
75
+ #
76
+ # In this case, make sure you return a String from
77
+ # your handler encoded in the default_internal. Since
78
+ # you are handling out-of-band metadata, you are
79
+ # also responsible for alerting the user to any
80
+ # problems with converting the user's data to
81
+ # the <tt>default_internal</tt>.
82
+ #
83
+ # To do so, simply raise +WrongEncodingError+ as follows:
84
+ #
85
+ # raise WrongEncodingError.new(
86
+ # problematic_string,
87
+ # expected_encoding
88
+ # )
89
+
90
+ eager_autoload do
91
+ autoload :Error
92
+ autoload :Handlers
93
+ autoload :Text
94
+ autoload :Types
95
+ end
96
+
97
+ extend Template::Handlers
98
+
99
+ attr_accessor :locals, :formats, :virtual_path
100
+
101
+ attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
102
+
103
+ # This finalizer is needed (and exactly with a proc inside another proc)
104
+ # otherwise templates leak in development.
105
+ Finalizer = proc do |method_name, mod|
106
+ proc do
107
+ mod.module_eval do
108
+ remove_possible_method method_name
109
+ end
110
+ end
111
+ end
112
+
113
+ def initialize(source, identifier, handler, details)
114
+ format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
115
+
116
+ @source = source
117
+ @identifier = identifier
118
+ @handler = handler
119
+ @compiled = false
120
+ @original_encoding = nil
121
+ @locals = details[:locals] || []
122
+ @virtual_path = details[:virtual_path]
123
+ @updated_at = details[:updated_at] || Time.now
124
+ @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
125
+ @compile_mutex = Mutex.new
126
+ end
127
+
128
+ # Returns if the underlying handler supports streaming. If so,
129
+ # a streaming buffer *may* be passed when it start rendering.
130
+ def supports_streaming?
131
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
132
+ end
133
+
134
+ # Render a template. If the template was not compiled yet, it is done
135
+ # exactly before rendering.
136
+ #
137
+ # This method is instrumented as "!render_template.action_view". Notice that
138
+ # we use a bang in this instrumentation because you don't want to
139
+ # consume this in production. This is only slow if it's being listened to.
140
+ def render(view, locals, buffer=nil, &block)
141
+ instrument("!render_template") do
142
+ compile!(view)
143
+ view.send(method_name, locals, buffer, &block)
144
+ end
145
+ rescue => e
146
+ handle_render_error(view, e)
147
+ end
148
+
149
+ def type
150
+ @type ||= Types[@formats.first] if @formats.first
151
+ end
152
+
153
+ # Receives a view object and return a template similar to self by using @virtual_path.
154
+ #
155
+ # This method is useful if you have a template object but it does not contain its source
156
+ # anymore since it was already compiled. In such cases, all you need to do is to call
157
+ # refresh passing in the view object.
158
+ #
159
+ # Notice this method raises an error if the template to be refreshed does not have a
160
+ # virtual path set (true just for inline templates).
161
+ def refresh(view)
162
+ raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
163
+ lookup = view.lookup_context
164
+ pieces = @virtual_path.split("/")
165
+ name = pieces.pop
166
+ partial = !!name.sub!(/^_/, "")
167
+ lookup.disable_cache do
168
+ lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
169
+ end
170
+ end
171
+
172
+ def inspect
173
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
174
+ end
175
+
176
+ # This method is responsible for properly setting the encoding of the
177
+ # source. Until this point, we assume that the source is BINARY data.
178
+ # If no additional information is supplied, we assume the encoding is
179
+ # the same as <tt>Encoding.default_external</tt>.
180
+ #
181
+ # The user can also specify the encoding via a comment on the first
182
+ # line of the template (# encoding: NAME-OF-ENCODING). This will work
183
+ # with any template engine, as we process out the encoding comment
184
+ # before passing the source on to the template engine, leaving a
185
+ # blank line in its stead.
186
+ def encode!
187
+ return unless source.encoding == Encoding::BINARY
188
+
189
+ # Look for # encoding: *. If we find one, we'll encode the
190
+ # String in that encoding, otherwise, we'll use the
191
+ # default external encoding.
192
+ if source.sub!(/\A#{ENCODING_FLAG}/, '')
193
+ encoding = magic_encoding = $1
194
+ else
195
+ encoding = Encoding.default_external
196
+ end
197
+
198
+ # Tag the source with the default external encoding
199
+ # or the encoding specified in the file
200
+ source.force_encoding(encoding)
201
+
202
+ # If the user didn't specify an encoding, and the handler
203
+ # handles encodings, we simply pass the String as is to
204
+ # the handler (with the default_external tag)
205
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
206
+ source
207
+ # Otherwise, if the String is valid in the encoding,
208
+ # encode immediately to default_internal. This means
209
+ # that if a handler doesn't handle encodings, it will
210
+ # always get Strings in the default_internal
211
+ elsif source.valid_encoding?
212
+ source.encode!
213
+ # Otherwise, since the String is invalid in the encoding
214
+ # specified, raise an exception
215
+ else
216
+ raise WrongEncodingError.new(source, encoding)
217
+ end
218
+ end
219
+
220
+ protected
221
+
222
+ # Compile a template. This method ensures a template is compiled
223
+ # just once and removes the source after it is compiled.
224
+ def compile!(view) #:nodoc:
225
+ return if @compiled
226
+
227
+ # Templates can be used concurrently in threaded environments
228
+ # so compilation and any instance variable modification must
229
+ # be synchronized
230
+ @compile_mutex.synchronize do
231
+ # Any thread holding this lock will be compiling the template needed
232
+ # by the threads waiting. So re-check the @compiled flag to avoid
233
+ # re-compilation
234
+ return if @compiled
235
+
236
+ if view.is_a?(ActionView::CompiledTemplates)
237
+ mod = ActionView::CompiledTemplates
238
+ else
239
+ mod = view.singleton_class
240
+ end
241
+
242
+ instrument("!compile_template") do
243
+ compile(view, mod)
244
+ end
245
+
246
+ # Just discard the source if we have a virtual path. This
247
+ # means we can get the template back.
248
+ @source = nil if @virtual_path
249
+ @compiled = true
250
+ end
251
+ end
252
+
253
+ # Among other things, this method is responsible for properly setting
254
+ # the encoding of the compiled template.
255
+ #
256
+ # If the template engine handles encodings, we send the encoded
257
+ # String to the engine without further processing. This allows
258
+ # the template engine to support additional mechanisms for
259
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
260
+ #
261
+ # Otherwise, after we figure out the correct encoding, we then
262
+ # encode the source into <tt>Encoding.default_internal</tt>.
263
+ # In general, this means that templates will be UTF-8 inside of Rails,
264
+ # regardless of the original source encoding.
265
+ def compile(view, mod) #:nodoc:
266
+ encode!
267
+ method_name = self.method_name
268
+ code = @handler.call(self)
269
+
270
+ # Make sure that the resulting String to be eval'd is in the
271
+ # encoding of the code
272
+ source = <<-end_src
273
+ def #{method_name}(local_assigns, output_buffer)
274
+ _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
275
+ ensure
276
+ @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
277
+ end
278
+ end_src
279
+
280
+ # Make sure the source is in the encoding of the returned code
281
+ source.force_encoding(code.encoding)
282
+
283
+ # In case we get back a String from a handler that is not in
284
+ # BINARY or the default_internal, encode it to the default_internal
285
+ source.encode!
286
+
287
+ # Now, validate that the source we got back from the template
288
+ # handler is valid in the default_internal. This is for handlers
289
+ # that handle encoding but screw up
290
+ unless source.valid_encoding?
291
+ raise WrongEncodingError.new(@source, Encoding.default_internal)
292
+ end
293
+
294
+ begin
295
+ mod.module_eval(source, identifier, 0)
296
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
297
+ rescue => e # errors from template code
298
+ if logger = (view && view.logger)
299
+ logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
300
+ logger.debug "Function body: #{source}"
301
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
302
+ end
303
+
304
+ raise ActionView::Template::Error.new(self, e)
305
+ end
306
+ end
307
+
308
+ def handle_render_error(view, e) #:nodoc:
309
+ if e.is_a?(Template::Error)
310
+ e.sub_template_of(self)
311
+ raise e
312
+ else
313
+ template = self
314
+ unless template.source
315
+ template = refresh(view)
316
+ template.encode!
317
+ end
318
+ raise Template::Error.new(template, e)
319
+ end
320
+ end
321
+
322
+ def locals_code #:nodoc:
323
+ # Double assign to suppress the dreaded 'assigned but unused variable' warning
324
+ @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join
325
+ end
326
+
327
+ def method_name #:nodoc:
328
+ @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
329
+ end
330
+
331
+ def identifier_method_name #:nodoc:
332
+ inspect.gsub(/[^a-z_]/, '_')
333
+ end
334
+
335
+ def instrument(action, &block)
336
+ payload = { virtual_path: @virtual_path, identifier: @identifier }
337
+ ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
338
+ end
339
+ end
340
+ end
@@ -0,0 +1,141 @@
1
+ require "active_support/core_ext/enumerable"
2
+
3
+ module ActionView
4
+ # = Action View Errors
5
+ class ActionViewError < StandardError #:nodoc:
6
+ end
7
+
8
+ class EncodingError < StandardError #:nodoc:
9
+ end
10
+
11
+ class MissingRequestError < StandardError #:nodoc:
12
+ end
13
+
14
+ class WrongEncodingError < EncodingError #:nodoc:
15
+ def initialize(string, encoding)
16
+ @string, @encoding = string, encoding
17
+ end
18
+
19
+ def message
20
+ @string.force_encoding(Encoding::ASCII_8BIT)
21
+ "Your template was not saved as valid #{@encoding}. Please " \
22
+ "either specify #{@encoding} as the encoding for your template " \
23
+ "in your text editor, or mark the template with its " \
24
+ "encoding by inserting the following as the first line " \
25
+ "of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
26
+ "The source of your template was:\n\n#{@string}"
27
+ end
28
+ end
29
+
30
+ class MissingTemplate < ActionViewError #:nodoc:
31
+ attr_reader :path
32
+
33
+ def initialize(paths, path, prefixes, partial, details, *)
34
+ @path = path
35
+ prefixes = Array(prefixes)
36
+ template_type = if partial
37
+ "partial"
38
+ elsif path =~ /layouts/i
39
+ 'layout'
40
+ else
41
+ 'template'
42
+ end
43
+
44
+ if partial && path.present?
45
+ path = path.sub(%r{([^/]+)$}, "_\\1")
46
+ end
47
+ searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
48
+
49
+ out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
50
+ out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
51
+ super out
52
+ end
53
+ end
54
+
55
+ class Template
56
+ # The Template::Error exception is raised when the compilation or rendering of the template
57
+ # fails. This exception then gathers a bunch of intimate details and uses it to report a
58
+ # precise exception message.
59
+ class Error < ActionViewError #:nodoc:
60
+ SOURCE_CODE_RADIUS = 3
61
+
62
+ attr_reader :original_exception
63
+
64
+ def initialize(template, original_exception)
65
+ super(original_exception.message)
66
+ @template, @original_exception = template, original_exception
67
+ @sub_templates = nil
68
+ set_backtrace(original_exception.backtrace)
69
+ end
70
+
71
+ def file_name
72
+ @template.identifier
73
+ end
74
+
75
+ def sub_template_message
76
+ if @sub_templates
77
+ "Trace of template inclusion: " +
78
+ @sub_templates.collect { |template| template.inspect }.join(", ")
79
+ else
80
+ ""
81
+ end
82
+ end
83
+
84
+ def source_extract(indentation = 0, output = :console)
85
+ return unless num = line_number
86
+ num = num.to_i
87
+
88
+ source_code = @template.source.split("\n")
89
+
90
+ start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
91
+ end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
92
+
93
+ indent = end_on_line.to_s.size + indentation
94
+ return unless source_code = source_code[start_on_line..end_on_line]
95
+
96
+ formatted_code_for(source_code, start_on_line, indent, output)
97
+ end
98
+
99
+ def sub_template_of(template_path)
100
+ @sub_templates ||= []
101
+ @sub_templates << template_path
102
+ end
103
+
104
+ def line_number
105
+ @line_number ||=
106
+ if file_name
107
+ regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
108
+ $1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
109
+ end
110
+ end
111
+
112
+ def annoted_source_code
113
+ source_extract(4)
114
+ end
115
+
116
+ private
117
+
118
+ def source_location
119
+ if line_number
120
+ "on line ##{line_number} of "
121
+ else
122
+ 'in '
123
+ end + file_name
124
+ end
125
+
126
+ def formatted_code_for(source_code, line_counter, indent, output)
127
+ start_value = (output == :html) ? {} : ""
128
+ source_code.inject(start_value) do |result, line|
129
+ line_counter += 1
130
+ if output == :html
131
+ result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
132
+ else
133
+ result << "%#{indent}s: %s\n" % [line_counter, line]
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ TemplateError = Template::Error
141
+ end