tilt 1.3.7 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,9 @@
1
1
  ## master
2
2
 
3
+ ## 1.4.0 (2013-05-01)
4
+
5
+ * Better encoding support
6
+
3
7
  ## 1.3.7 (2013-04-09)
4
8
 
5
9
  * Erubis: Check for the correct constant (#183, mattwildig)
data/README.md CHANGED
@@ -194,6 +194,27 @@ template, but if you depend on a specific implementation, you should use #prefer
194
194
  When a file extension has a preferred template class, Tilt will *always* use
195
195
  that class, even if it raises an exception.
196
196
 
197
+ Encodings
198
+ ---------
199
+
200
+ Tilt needs to know the encoding of the template in order to work properly:
201
+
202
+ Tilt will use `Encoding.default_external` as the encoding when reading external
203
+ files. If you're mostly working with one encoding (e.g. UTF-8) we *highly*
204
+ recommend setting this option. When providing a custom reader block (`Tilt.new
205
+ { custom_string }`) you'll have ensure the string is properly encoded yourself.
206
+
207
+ Most of the template engines in Tilt also allows you to override the encoding
208
+ using the `:default_encoding`-option:
209
+
210
+ ```ruby
211
+ tmpl = Tilt.new('hello.erb', :default_encoding => 'Big5')
212
+ ```
213
+
214
+ Ultimately it's up to the template engine how to handle the encoding: It might
215
+ respect `:default_encoding`, it might always assume it's UTF-8 (like
216
+ CoffeScript), or it can do its own encoding detection.
217
+
197
218
  Template Compilation
198
219
  --------------------
199
220
 
@@ -1,5 +1,5 @@
1
1
  module Tilt
2
- VERSION = '1.3.7'
2
+ VERSION = '1.4.0'
3
3
 
4
4
  @preferred_mappings = Hash.new
5
5
  @template_mappings = Hash.new { |h, k| h[k] = [] }
@@ -65,11 +65,37 @@ module Tilt
65
65
  @default_encoding = @options.delete :default_encoding
66
66
 
67
67
  # load template data and prepare (uses binread to avoid encoding issues)
68
- @reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
68
+ @reader = block || lambda { |t| read_template_file }
69
69
  @data = @reader.call(self)
70
+
71
+ if @data.respond_to?(:force_encoding)
72
+ @data.force_encoding(default_encoding) if default_encoding
73
+
74
+ if !@data.valid_encoding?
75
+ raise Encoding::InvalidByteSequenceError, "#{eval_file} is not valid #{@data.encoding}"
76
+ end
77
+ end
78
+
70
79
  prepare
71
80
  end
72
81
 
82
+ # The encoding of the source data. Defaults to the
83
+ # default_encoding-option if present. You may override this method
84
+ # in your template class if you have a better hint of the data's
85
+ # encoding.
86
+ def default_encoding
87
+ @default_encoding
88
+ end
89
+
90
+ def read_template_file
91
+ data = File.open(file, 'rb') { |io| io.read }
92
+ if data.respond_to?(:force_encoding)
93
+ # Set it to the default external (without verifying)
94
+ data.force_encoding(Encoding.default_external) if Encoding.default_external
95
+ end
96
+ data
97
+ end
98
+
73
99
  # Render the template in the given scope with the locals specified. If a
74
100
  # block is given, it is typically available within the template via
75
101
  # +yield+.
@@ -156,26 +182,29 @@ module Tilt
156
182
  def precompiled(locals)
157
183
  preamble = precompiled_preamble(locals)
158
184
  template = precompiled_template(locals)
159
- magic_comment = extract_magic_comment(template)
160
- if magic_comment
161
- # Magic comment e.g. "# coding: utf-8" has to be in the first line.
162
- # So we copy the magic comment to the first line.
163
- preamble = magic_comment + "\n" + preamble
185
+ postamble = precompiled_postamble(locals)
186
+ source = ''
187
+
188
+ # Ensure that our generated source code has the same encoding as the
189
+ # the source code generated by the template engine.
190
+ if source.respond_to?(:force_encoding)
191
+ template_encoding = extract_encoding(template)
192
+
193
+ source.force_encoding(template_encoding)
194
+ template.force_encoding(template_encoding)
164
195
  end
165
- parts = [
166
- preamble,
167
- template,
168
- precompiled_postamble(locals)
169
- ]
170
- [parts.join("\n"), preamble.count("\n") + 1]
196
+
197
+ source << preamble << "\n" << template << "\n" << postamble
198
+
199
+ [source, preamble.count("\n")+1]
171
200
  end
172
201
 
173
202
  # A string containing the (Ruby) source code for the template. The
174
- # default Template#evaluate implementation requires either this method
175
- # or the #precompiled method be overridden. When defined, the base
176
- # Template guarantees correct file/line handling, locals support, custom
177
- # scopes, and support for template compilation when the scope object
178
- # allows it.
203
+ # default Template#evaluate implementation requires either this
204
+ # method or the #precompiled method be overridden. When defined,
205
+ # the base Template guarantees correct file/line handling, locals
206
+ # support, custom scopes, proper encoding, and support for template
207
+ # compilation.
179
208
  def precompiled_template(locals)
180
209
  raise NotImplementedError
181
210
  end
@@ -212,8 +241,13 @@ module Tilt
212
241
  def compile_template_method(locals)
213
242
  source, offset = precompiled(locals)
214
243
  method_name = "__tilt_#{Thread.current.object_id.abs}"
215
- method_source = <<-RUBY
216
- #{extract_magic_comment source}
244
+ method_source = ""
245
+
246
+ if method_source.respond_to?(:force_encoding)
247
+ method_source.force_encoding(source.encoding)
248
+ end
249
+
250
+ method_source << <<-RUBY
217
251
  TOPOBJECT.class_eval do
218
252
  def #{method_name}(locals)
219
253
  Thread.current[:tilt_vars] = [self, locals]
@@ -234,13 +268,22 @@ module Tilt
234
268
  method
235
269
  end
236
270
 
271
+ def extract_encoding(script)
272
+ extract_magic_comment(script) || script.encoding
273
+ end
274
+
237
275
  def extract_magic_comment(script)
238
- comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
239
- if comment && !%w[ascii-8bit binary].include?($1.downcase)
240
- comment
241
- elsif @default_encoding
242
- "# coding: #{@default_encoding}"
276
+ binary script do
277
+ script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
243
278
  end
244
279
  end
280
+
281
+ def binary(string)
282
+ original_encoding = string.encoding
283
+ string.force_encoding(Encoding::BINARY)
284
+ yield
285
+ ensure
286
+ string.force_encoding(original_encoding)
287
+ end
245
288
  end
246
289
  end
@@ -18,7 +18,7 @@ module MarkdownTests
18
18
  end
19
19
 
20
20
  def normalize(html)
21
- Nokogiri::HTML.fragment(html).to_s
21
+ Nokogiri::HTML.fragment(html).to_s.strip
22
22
  end
23
23
 
24
24
  def nrender(text, options = {})
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  require 'contest'
2
3
  require 'tilt'
3
4
  require 'tempfile'
@@ -176,4 +177,100 @@ class TiltTemplateTest < Test::Unit::TestCase
176
177
  inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{CONSTANT}!' }
177
178
  assert_equal "Hey Bob!", inst.render(Person.new("Joe"))
178
179
  end
180
+
181
+ ##
182
+ # Encodings
183
+
184
+ class DynamicMockTemplate < MockTemplate
185
+ def precompiled_template(locals)
186
+ options[:code]
187
+ end
188
+ end
189
+
190
+ class UTF8Template < MockTemplate
191
+ def default_encoding
192
+ Encoding::UTF_8
193
+ end
194
+ end
195
+
196
+ if ''.respond_to?(:encoding)
197
+ original_encoding = Encoding.default_external
198
+
199
+ setup do
200
+ @file = Tempfile.open('template')
201
+ @file.puts "stuff"
202
+ @file.close
203
+ @template = @file.path
204
+ end
205
+
206
+ teardown do
207
+ Encoding.default_external = original_encoding
208
+ Encoding.default_internal = nil
209
+ @file.delete
210
+ end
211
+
212
+ test "reading from file assumes default external encoding" do
213
+ Encoding.default_external = 'Big5'
214
+ inst = MockTemplate.new(@template)
215
+ assert_equal 'Big5', inst.data.encoding.to_s
216
+ end
217
+
218
+ test "reading from file with a :default_encoding overrides default external" do
219
+ Encoding.default_external = 'Big5'
220
+ inst = MockTemplate.new(@template, :default_encoding => 'GBK')
221
+ assert_equal 'GBK', inst.data.encoding.to_s
222
+ end
223
+
224
+ test "reading from file with default_internal set does no transcoding" do
225
+ Encoding.default_internal = 'utf-8'
226
+ Encoding.default_external = 'Big5'
227
+ inst = MockTemplate.new(@template)
228
+ assert_equal 'Big5', inst.data.encoding.to_s
229
+ end
230
+
231
+ test "using provided template data verbatim when given as string" do
232
+ Encoding.default_internal = 'Big5'
233
+ inst = MockTemplate.new(@template) { "blah".force_encoding('GBK') }
234
+ assert_equal 'GBK', inst.data.encoding.to_s
235
+ end
236
+
237
+ test "uses the template from the generated source code" do
238
+ tmpl = "ふが"
239
+ code = tmpl.inspect.encode('Shift_JIS')
240
+ inst = DynamicMockTemplate.new(:code => code) { '' }
241
+ res = inst.render
242
+ assert_equal 'Shift_JIS', res.encoding.to_s
243
+ assert_equal tmpl, res.encode(tmpl.encoding)
244
+ end
245
+
246
+ test "uses the magic comment from the generated source code" do
247
+ tmpl = "ふが"
248
+ code = ("# coding: Shift_JIS\n" + tmpl.inspect).encode('Shift_JIS')
249
+ # Set it to an incorrect encoding
250
+ code.force_encoding('UTF-8')
251
+
252
+ inst = DynamicMockTemplate.new(:code => code) { '' }
253
+ res = inst.render
254
+ assert_equal 'Shift_JIS', res.encoding.to_s
255
+ assert_equal tmpl, res.encode(tmpl.encoding)
256
+ end
257
+
258
+ test "uses #default_encoding instead of default_external" do
259
+ Encoding.default_external = 'Big5'
260
+ inst = UTF8Template.new(@template)
261
+ assert_equal 'UTF-8', inst.data.encoding.to_s
262
+ end
263
+
264
+ test "uses #default_encoding instead of current encoding" do
265
+ tmpl = "".force_encoding('Big5')
266
+ inst = UTF8Template.new(@template) { tmpl }
267
+ assert_equal 'UTF-8', inst.data.encoding.to_s
268
+ end
269
+
270
+ test "raises error if the encoding is not valid" do
271
+ assert_raises(Encoding::InvalidByteSequenceError) do
272
+ UTF8Template.new(@template) { "\xe4" }
273
+ end
274
+ end
275
+ end
179
276
  end
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'tilt'
6
- s.version = '1.3.7'
7
- s.date = '2013-04-09'
6
+ s.version = '1.4.0'
7
+ s.date = '2013-05-01'
8
8
 
9
9
  s.description = "Generic interface to multiple Ruby template engines"
10
10
  s.summary = s.description
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tilt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.7
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-09 00:00:00.000000000 Z
12
+ date: 2013-05-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor