tilt 1.3.7 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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