tilt 1.3.7 → 1.4.1

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.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## master
2
2
 
3
+ ## 1.4.1 (2013-05-08)
4
+
5
+ * Support Arrays in pre/postambles (#193, jbwiv)
6
+
7
+ ## 1.4.0 (2013-05-01)
8
+
9
+ * Better encoding support
10
+
3
11
  ## 1.3.7 (2013-04-09)
4
12
 
5
13
  * 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
 
data/TEMPLATES.md CHANGED
@@ -305,9 +305,9 @@ time when using this template engine within a threaded environment.
305
305
 
306
306
  ### See also
307
307
 
308
- * [Liquid for Programmers](http://wiki.github.com/tobi/liquid/liquid-for-programmers)
308
+ * [Liquid for Programmers](https://wiki.github.com/Shopify/liquid/liquid-for-programmers)
309
309
  * [Liquid Docs](http://liquid.rubyforge.org/)
310
- * GitHub: [tobi/liquid](http://github.com/tobi/liquid/)
310
+ * GitHub: [Shopify/liquid](https://github.com/Shopify/liquid/)
311
311
 
312
312
 
313
313
  <a name='radius'></a>
data/lib/tilt/template.rb CHANGED
@@ -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,32 @@ 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
+ # https://github.com/rtomayko/tilt/issues/193
198
+ warn "precompiled_preamble should return String (not Array)" if preamble.is_a?(Array)
199
+ warn "precompiled_postamble should return String (not Array)" if postamble.is_a?(Array)
200
+ source << [preamble, template, postamble].join("\n")
201
+
202
+ [source, preamble.count("\n")+1]
171
203
  end
172
204
 
173
205
  # 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.
206
+ # default Template#evaluate implementation requires either this
207
+ # method or the #precompiled method be overridden. When defined,
208
+ # the base Template guarantees correct file/line handling, locals
209
+ # support, custom scopes, proper encoding, and support for template
210
+ # compilation.
179
211
  def precompiled_template(locals)
180
212
  raise NotImplementedError
181
213
  end
@@ -212,8 +244,13 @@ module Tilt
212
244
  def compile_template_method(locals)
213
245
  source, offset = precompiled(locals)
214
246
  method_name = "__tilt_#{Thread.current.object_id.abs}"
215
- method_source = <<-RUBY
216
- #{extract_magic_comment source}
247
+ method_source = ""
248
+
249
+ if method_source.respond_to?(:force_encoding)
250
+ method_source.force_encoding(source.encoding)
251
+ end
252
+
253
+ method_source << <<-RUBY
217
254
  TOPOBJECT.class_eval do
218
255
  def #{method_name}(locals)
219
256
  Thread.current[:tilt_vars] = [self, locals]
@@ -234,13 +271,22 @@ module Tilt
234
271
  method
235
272
  end
236
273
 
274
+ def extract_encoding(script)
275
+ extract_magic_comment(script) || script.encoding
276
+ end
277
+
237
278
  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}"
279
+ binary script do
280
+ script[/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/n, 1]
243
281
  end
244
282
  end
283
+
284
+ def binary(string)
285
+ original_encoding = string.encoding
286
+ string.force_encoding(Encoding::BINARY)
287
+ yield
288
+ ensure
289
+ string.force_encoding(original_encoding)
290
+ end
245
291
  end
246
292
  end
data/lib/tilt.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Tilt
2
- VERSION = '1.3.7'
2
+ VERSION = '1.4.1'
3
3
 
4
4
  @preferred_mappings = Hash.new
5
5
  @template_mappings = Hash.new { |h, k| h[k] = [] }
@@ -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'
@@ -152,6 +153,53 @@ class TiltTemplateTest < Test::Unit::TestCase
152
153
  assert_equal "1 + 2 = 3", inst.render(Object.new, '_answer' => 3)
153
154
  end
154
155
 
156
+ class CustomGeneratingMockTemplate < PreparingMockTemplate
157
+ def precompiled_template(locals)
158
+ data
159
+ end
160
+
161
+ def precompiled_preamble(locals)
162
+ options.fetch(:preamble)
163
+ end
164
+
165
+ def precompiled_postamble(locals)
166
+ options.fetch(:postamble)
167
+ end
168
+ end
169
+
170
+ test "supports pre/postamble" do
171
+ inst = CustomGeneratingMockTemplate.new(
172
+ :preamble => 'buf = []',
173
+ :postamble => 'buf.join'
174
+ ) { 'buf << 1' }
175
+
176
+ assert_equal "1", inst.render
177
+ end
178
+
179
+ # Special-case for Haml
180
+ # https://github.com/rtomayko/tilt/issues/193
181
+ test "supports Array pre/postambles" do
182
+ inst = CustomGeneratingMockTemplate.new(
183
+ :preamble => ['buf = ', '[]'],
184
+ :postamble => ['buf.', 'join']
185
+ ) { 'buf << 1' }
186
+
187
+ # TODO: Use assert_output when we swicth to MiniTest
188
+ warns = <<-EOF
189
+ precompiled_preamble should return String (not Array)
190
+ precompiled_postamble should return String (not Array)
191
+ EOF
192
+
193
+ begin
194
+ require 'stringio'
195
+ $stderr = StringIO.new
196
+ assert_equal "1", inst.render
197
+ assert_equal warns, $stderr.string
198
+ ensure
199
+ $stderr = STDERR
200
+ end
201
+ end
202
+
155
203
  class Person
156
204
  CONSTANT = "Bob"
157
205
 
@@ -176,4 +224,100 @@ class TiltTemplateTest < Test::Unit::TestCase
176
224
  inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{CONSTANT}!' }
177
225
  assert_equal "Hey Bob!", inst.render(Person.new("Joe"))
178
226
  end
227
+
228
+ ##
229
+ # Encodings
230
+
231
+ class DynamicMockTemplate < MockTemplate
232
+ def precompiled_template(locals)
233
+ options[:code]
234
+ end
235
+ end
236
+
237
+ class UTF8Template < MockTemplate
238
+ def default_encoding
239
+ Encoding::UTF_8
240
+ end
241
+ end
242
+
243
+ if ''.respond_to?(:encoding)
244
+ original_encoding = Encoding.default_external
245
+
246
+ setup do
247
+ @file = Tempfile.open('template')
248
+ @file.puts "stuff"
249
+ @file.close
250
+ @template = @file.path
251
+ end
252
+
253
+ teardown do
254
+ Encoding.default_external = original_encoding
255
+ Encoding.default_internal = nil
256
+ @file.delete
257
+ end
258
+
259
+ test "reading from file assumes default external encoding" do
260
+ Encoding.default_external = 'Big5'
261
+ inst = MockTemplate.new(@template)
262
+ assert_equal 'Big5', inst.data.encoding.to_s
263
+ end
264
+
265
+ test "reading from file with a :default_encoding overrides default external" do
266
+ Encoding.default_external = 'Big5'
267
+ inst = MockTemplate.new(@template, :default_encoding => 'GBK')
268
+ assert_equal 'GBK', inst.data.encoding.to_s
269
+ end
270
+
271
+ test "reading from file with default_internal set does no transcoding" do
272
+ Encoding.default_internal = 'utf-8'
273
+ Encoding.default_external = 'Big5'
274
+ inst = MockTemplate.new(@template)
275
+ assert_equal 'Big5', inst.data.encoding.to_s
276
+ end
277
+
278
+ test "using provided template data verbatim when given as string" do
279
+ Encoding.default_internal = 'Big5'
280
+ inst = MockTemplate.new(@template) { "blah".force_encoding('GBK') }
281
+ assert_equal 'GBK', inst.data.encoding.to_s
282
+ end
283
+
284
+ test "uses the template from the generated source code" do
285
+ tmpl = "ふが"
286
+ code = tmpl.inspect.encode('Shift_JIS')
287
+ inst = DynamicMockTemplate.new(:code => code) { '' }
288
+ res = inst.render
289
+ assert_equal 'Shift_JIS', res.encoding.to_s
290
+ assert_equal tmpl, res.encode(tmpl.encoding)
291
+ end
292
+
293
+ test "uses the magic comment from the generated source code" do
294
+ tmpl = "ふが"
295
+ code = ("# coding: Shift_JIS\n" + tmpl.inspect).encode('Shift_JIS')
296
+ # Set it to an incorrect encoding
297
+ code.force_encoding('UTF-8')
298
+
299
+ inst = DynamicMockTemplate.new(:code => code) { '' }
300
+ res = inst.render
301
+ assert_equal 'Shift_JIS', res.encoding.to_s
302
+ assert_equal tmpl, res.encode(tmpl.encoding)
303
+ end
304
+
305
+ test "uses #default_encoding instead of default_external" do
306
+ Encoding.default_external = 'Big5'
307
+ inst = UTF8Template.new(@template)
308
+ assert_equal 'UTF-8', inst.data.encoding.to_s
309
+ end
310
+
311
+ test "uses #default_encoding instead of current encoding" do
312
+ tmpl = "".force_encoding('Big5')
313
+ inst = UTF8Template.new(@template) { tmpl }
314
+ assert_equal 'UTF-8', inst.data.encoding.to_s
315
+ end
316
+
317
+ test "raises error if the encoding is not valid" do
318
+ assert_raises(Encoding::InvalidByteSequenceError) do
319
+ UTF8Template.new(@template) { "\xe4" }
320
+ end
321
+ end
322
+ end
179
323
  end
data/tilt.gemspec CHANGED
@@ -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.1'
7
+ s.date = '2013-05-08'
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.1
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-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -518,3 +518,4 @@ test_files:
518
518
  - test/tilt_test.rb
519
519
  - test/tilt_wikiclothtemplate_test.rb
520
520
  - test/tilt_yajltemplate_test.rb
521
+ has_rdoc: