template.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. module Tilt
  2. TOPOBJECT = defined?(BasicObject) ? BasicObject : Object
  3. # Base class for template implementations. Subclasses must implement
  4. # the #prepare method and one of the #evaluate or #precompiled_template
  5. # methods.
  6. class Template
  7. # Template source; loaded from a file or given directly.
  8. attr_reader :data
  9. # The name of the file where the template data was loaded from.
  10. attr_reader :file
  11. # The line number in #file where template data was loaded from.
  12. attr_reader :line
  13. # A Hash of template engine specific options. This is passed directly
  14. # to the underlying engine and is not used by the generic template
  15. # interface.
  16. attr_reader :options
  17. # Used to determine if this class's initialize_engine method has
  18. # been called yet.
  19. @engine_initialized = false
  20. class << self
  21. attr_accessor :engine_initialized
  22. alias engine_initialized? engine_initialized
  23. attr_accessor :default_mime_type
  24. end
  25. # Create a new template with the file, line, and options specified. By
  26. # default, template data is read from the file. When a block is given,
  27. # it should read template data and return as a String. When file is nil,
  28. # a block is required.
  29. #
  30. # All arguments are optional.
  31. def initialize(file=nil, line=1, options={}, &block)
  32. @file, @line, @options = nil, 1, {}
  33. [options, line, file].compact.each do |arg|
  34. case
  35. when arg.respond_to?(:to_str) ; @file = arg.to_str
  36. when arg.respond_to?(:to_int) ; @line = arg.to_int
  37. when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
  38. else raise TypeError
  39. end
  40. end
  41. raise ArgumentError, "file or block required" if (@file || block).nil?
  42. # call the initialize_engine method if this is the very first time
  43. # an instance of this class has been created.
  44. if !self.class.engine_initialized?
  45. initialize_engine
  46. self.class.engine_initialized = true
  47. end
  48. # used to hold compiled template methods
  49. @compiled_method = {}
  50. # used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
  51. # currently only used if template compiles to ruby
  52. @default_encoding = @options.delete :default_encoding
  53. # load template data and prepare (uses binread to avoid encoding issues)
  54. @reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
  55. @data = @reader.call(self)
  56. prepare
  57. end
  58. # Render the template in the given scope with the locals specified. If a
  59. # block is given, it is typically available within the template via
  60. # +yield+.
  61. def render(scope=Object.new, locals={}, &block)
  62. evaluate scope, locals || {}, &block
  63. end
  64. # The basename of the template file.
  65. def basename(suffix='')
  66. File.basename(file, suffix) if file
  67. end
  68. # The template file's basename with all extensions chomped off.
  69. def name
  70. basename.split('.', 2).first if basename
  71. end
  72. # The filename used in backtraces to describe the template.
  73. def eval_file
  74. file || '(__TEMPLATE__)'
  75. end
  76. protected
  77. # Called once and only once for each template subclass the first time
  78. # the template class is initialized. This should be used to require the
  79. # underlying template library and perform any initial setup.
  80. def initialize_engine
  81. end
  82. # Like Kernel::require but issues a warning urging a manual require when
  83. # running under a threaded environment.
  84. def require_template_library(name)
  85. if Thread.list.size > 1
  86. warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
  87. "explicit require '#{name}' suggested."
  88. end
  89. require name
  90. end
  91. # Do whatever preparation is necessary to setup the underlying template
  92. # engine. Called immediately after template data is loaded. Instance
  93. # variables set in this method are available when #evaluate is called.
  94. #
  95. # Subclasses must provide an implementation of this method.
  96. def prepare
  97. if respond_to?(:compile!)
  98. # backward compat with tilt < 0.6; just in case
  99. warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
  100. compile!
  101. else
  102. raise NotImplementedError
  103. end
  104. end
  105. def evaluate(scope, locals, &block)
  106. cached_evaluate(scope, locals, &block)
  107. end
  108. # Process the template and return the result. The first time this
  109. # method is called, the template source is evaluated with instance_eval.
  110. # On the sequential method calls it will compile the template to an
  111. # unbound method which will lead to better performance. In any case,
  112. # template executation is guaranteed to be performed in the scope object
  113. # with the locals specified and with support for yielding to the block.
  114. def cached_evaluate(scope, locals, &block)
  115. # Redefine itself to use method compilation the next time:
  116. def self.cached_evaluate(scope, locals, &block)
  117. method = compiled_method(locals.keys)
  118. method.bind(scope).call(locals, &block)
  119. end
  120. # Use instance_eval the first time:
  121. evaluate_source(scope, locals, &block)
  122. end
  123. # Generates all template source by combining the preamble, template, and
  124. # postamble and returns a two-tuple of the form: [source, offset], where
  125. # source is the string containing (Ruby) source code for the template and
  126. # offset is the integer line offset where line reporting should begin.
  127. #
  128. # Template subclasses may override this method when they need complete
  129. # control over source generation or want to adjust the default line
  130. # offset. In most cases, overriding the #precompiled_template method is
  131. # easier and more appropriate.
  132. def precompiled(locals)
  133. preamble = precompiled_preamble(locals)
  134. template = precompiled_template(locals)
  135. magic_comment = extract_magic_comment(template)
  136. if magic_comment
  137. # Magic comment e.g. "# coding: utf-8" has to be in the first line.
  138. # So we copy the magic comment to the first line.
  139. preamble = magic_comment + "\n" + preamble
  140. end
  141. parts = [
  142. preamble,
  143. template,
  144. precompiled_postamble(locals)
  145. ]
  146. [parts.join("\n"), preamble.count("\n") + 1]
  147. end
  148. # A string containing the (Ruby) source code for the template. The
  149. # default Template#evaluate implementation requires either this method
  150. # or the #precompiled method be overridden. When defined, the base
  151. # Template guarantees correct file/line handling, locals support, custom
  152. # scopes, and support for template compilation when the scope object
  153. # allows it.
  154. def precompiled_template(locals)
  155. raise NotImplementedError
  156. end
  157. # Generates preamble code for initializing template state, and performing
  158. # locals assignment. The default implementation performs locals
  159. # assignment only. Lines included in the preamble are subtracted from the
  160. # source line offset, so adding code to the preamble does not effect line
  161. # reporting in Kernel::caller and backtraces.
  162. def precompiled_preamble(locals)
  163. locals.map { |k,v| "#{k} = locals[#{k.inspect}]" }.join("\n")
  164. end
  165. # Generates postamble code for the precompiled template source. The
  166. # string returned from this method is appended to the precompiled
  167. # template source.
  168. def precompiled_postamble(locals)
  169. ''
  170. end
  171. # The compiled method for the locals keys provided.
  172. def compiled_method(locals_keys)
  173. @compiled_method[locals_keys] ||=
  174. compile_template_method(locals_keys)
  175. end
  176. private
  177. # Evaluate the template source in the context of the scope object.
  178. def evaluate_source(scope, locals, &block)
  179. source, offset = precompiled(locals)
  180. scope.instance_eval(source, eval_file, line - offset)
  181. end
  182. # JRuby doesn't allow Object#instance_eval to yield to the block it's
  183. # closed over. This is by design and (ostensibly) something that will
  184. # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
  185. # exhibits the behavior. More info here:
  186. #
  187. # http://jira.codehaus.org/browse/JRUBY-2599
  188. #
  189. # We redefine evaluate_source to work around this issues.
  190. if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
  191. undef evaluate_source
  192. def evaluate_source(scope, locals, &block)
  193. source, offset = precompiled(locals)
  194. file, lineno = eval_file, (line - offset)
  195. scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
  196. end
  197. end
  198. def compile_template_method(locals)
  199. source, offset = precompiled(locals)
  200. offset += 5
  201. method_name = "__tilt_#{Thread.current.object_id.abs}"
  202. Object.class_eval <<-RUBY, eval_file, line - offset
  203. #{extract_magic_comment source}
  204. TOPOBJECT.class_eval do
  205. def #{method_name}(locals)
  206. Thread.current[:tilt_vars] = [self, locals]
  207. class << self
  208. this, locals = Thread.current[:tilt_vars]
  209. this.instance_eval do
  210. #{source}
  211. end
  212. end
  213. end
  214. end
  215. RUBY
  216. unbind_compiled_method(method_name)
  217. end
  218. def unbind_compiled_method(method_name)
  219. method = TOPOBJECT.instance_method(method_name)
  220. TOPOBJECT.class_eval { remove_method(method_name) }
  221. method
  222. end
  223. def extract_magic_comment(script)
  224. comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
  225. return comment if comment and not %w[ascii-8bit binary].include?($1.downcase)
  226. "# coding: #{@default_encoding}" if @default_encoding
  227. end
  228. # Special case Ruby 1.9.1's broken yield.
  229. #
  230. # http://github.com/rtomayko/tilt/commit/20c01a5
  231. # http://redmine.ruby-lang.org/issues/show/3601
  232. #
  233. # Remove when 1.9.2 dominates 1.9.1 installs in the wild.
  234. if RUBY_VERSION =~ /^1.9.1/
  235. undef compile_template_method
  236. def compile_template_method(locals)
  237. source, offset = precompiled(locals)
  238. offset += 1
  239. method_name = "__tilt_#{Thread.current.object_id}"
  240. Object.class_eval <<-RUBY, eval_file, line - offset
  241. TOPOBJECT.class_eval do
  242. def #{method_name}(locals)
  243. #{source}
  244. end
  245. end
  246. RUBY
  247. unbind_compiled_method(method_name)
  248. end
  249. end
  250. end
  251. end