lib/tilt/template.rb in tilt-1.3.7 vs lib/tilt/template.rb in tilt-1.4.0
- old
+ new
@@ -63,15 +63,41 @@
# used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
# currently only used if template compiles to ruby
@default_encoding = @options.delete :default_encoding
# load template data and prepare (uses binread to avoid encoding issues)
- @reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
+ @reader = block || lambda { |t| read_template_file }
@data = @reader.call(self)
+
+ if @data.respond_to?(:force_encoding)
+ @data.force_encoding(default_encoding) if default_encoding
+
+ if !@data.valid_encoding?
+ raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
+ end
+ end
+
prepare
end
+ # The encoding of the source data. Defaults to the
+ # default_encoding-option if present. You may override this method
+ # in your template class if you have a better hint of the data's
+ # encoding.
+ def default_encoding
+ @default_encoding
+ end
+
+ def read_template_file
+ data = File.open(file, 'rb') { |io| io.read }
+ if data.respond_to?(:force_encoding)
+ # Set it to the default external (without verifying)
+ data.force_encoding(Encoding.default_external) if Encoding.default_external
+ end
+ data
+ end
+
# Render the template in the given scope with the locals specified. If a
# block is given, it is typically available within the template via
# +yield+.
def render(scope=Object.new, locals={}, &block)
evaluate scope, locals || {}, &block
@@ -154,30 +180,33 @@
# offset. In most cases, overriding the #precompiled_template method is
# easier and more appropriate.
def precompiled(locals)
preamble = precompiled_preamble(locals)
template = precompiled_template(locals)
- magic_comment = extract_magic_comment(template)
- if magic_comment
- # Magic comment e.g. "# coding: utf-8" has to be in the first line.
- # So we copy the magic comment to the first line.
- preamble = magic_comment + "\n" + preamble
+ postamble = precompiled_postamble(locals)
+ source = ''
+
+ # Ensure that our generated source code has the same encoding as the
+ # the source code generated by the template engine.
+ if source.respond_to?(:force_encoding)
+ template_encoding = extract_encoding(template)
+
+ source.force_encoding(template_encoding)
+ template.force_encoding(template_encoding)
end
- parts = [
- preamble,
- template,
- precompiled_postamble(locals)
- ]
- [parts.join("\n"), preamble.count("\n") + 1]
+
+ source << preamble << "\n" << template << "\n" << postamble
+
+ [source, preamble.count("\n")+1]
end
# A string containing the (Ruby) source code for the template. The
- # default Template#evaluate implementation requires either this method
- # or the #precompiled method be overridden. When defined, the base
- # Template guarantees correct file/line handling, locals support, custom
- # scopes, and support for template compilation when the scope object
- # allows it.
+ # default Template#evaluate implementation requires either this
+ # method or the #precompiled method be overridden. When defined,
+ # the base Template guarantees correct file/line handling, locals
+ # support, custom scopes, proper encoding, and support for template
+ # compilation.
def precompiled_template(locals)
raise NotImplementedError
end
# Generates preamble code for initializing template state, and performing
@@ -210,12 +239,17 @@
private
def compile_template_method(locals)
source, offset = precompiled(locals)
method_name = "__tilt_#{Thread.current.object_id.abs}"
- method_source = <<-RUBY
- #{extract_magic_comment source}
+ method_source = ""
+
+ if method_source.respond_to?(:force_encoding)
+ method_source.force_encoding(source.encoding)
+ end
+
+ method_source << <<-RUBY
TOPOBJECT.class_eval do
def #{method_name}(locals)
Thread.current[:tilt_vars] = [self, locals]
class << self
this, locals = Thread.current[:tilt_vars]
@@ -232,15 +266,24 @@
method = TOPOBJECT.instance_method(method_name)
TOPOBJECT.class_eval { remove_method(method_name) }
method
end
+ def extract_encoding(script)
+ extract_magic_comment(script) || script.encoding
+ end
+
def extract_magic_comment(script)
- comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
- if comment && !%w[ascii-8bit binary].include?($1.downcase)
- comment
- elsif @default_encoding
- "# coding: #{@default_encoding}"
+ binary script do
+ script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
end
+ end
+
+ def binary(string)
+ original_encoding = string.encoding
+ string.force_encoding(Encoding::BINARY)
+ yield
+ ensure
+ string.force_encoding(original_encoding)
end
end
end