tilt 1.2.2 → 1.3

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.
@@ -0,0 +1,131 @@
1
+ require 'tilt/template'
2
+
3
+ module Tilt
4
+ # Discount Markdown implementation. See:
5
+ # http://github.com/rtomayko/rdiscount
6
+ #
7
+ # RDiscount is a simple text filter. It does not support +scope+ or
8
+ # +locals+. The +:smart+ and +:filter_html+ options may be set true
9
+ # to enable those flags on the underlying RDiscount object.
10
+ class RDiscountTemplate < Template
11
+ self.default_mime_type = 'text/html'
12
+
13
+ ALIAS = {
14
+ :escape_html => :filter_html,
15
+ :smartypants => :smart
16
+ }
17
+
18
+ FLAGS = [:smart, :filter_html, :smartypants, :escape_html]
19
+
20
+ def flags
21
+ FLAGS.select { |flag| options[flag] }.map { |flag| ALIAS[flag] || flag }
22
+ end
23
+
24
+ def self.engine_initialized?
25
+ defined? ::RDiscount
26
+ end
27
+
28
+ def initialize_engine
29
+ require_template_library 'rdiscount'
30
+ end
31
+
32
+ def prepare
33
+ @engine = RDiscount.new(data, *flags)
34
+ @output = nil
35
+ end
36
+
37
+ def evaluate(scope, locals, &block)
38
+ @output ||= @engine.to_html
39
+ end
40
+ end
41
+
42
+ # Upskirt Markdown implementation. See:
43
+ # https://github.com/tanoku/redcarpet
44
+ #
45
+ # Compatible to RDiscount
46
+ class RedcarpetTemplate < RDiscountTemplate
47
+ self.default_mime_type = 'text/html'
48
+
49
+ def self.engine_initialized?
50
+ defined? ::RedcarpetCompat
51
+ end
52
+
53
+ def initialize_engine
54
+ require_template_library 'redcarpet'
55
+ end
56
+
57
+ def prepare
58
+ @engine = RedcarpetCompat.new(data, *flags)
59
+ @output = nil
60
+ end
61
+ end
62
+
63
+ # BlueCloth Markdown implementation. See:
64
+ # http://deveiate.org/projects/BlueCloth/
65
+ class BlueClothTemplate < Template
66
+ self.default_mime_type = 'text/html'
67
+
68
+ def self.engine_initialized?
69
+ defined? ::BlueCloth
70
+ end
71
+
72
+ def initialize_engine
73
+ require_template_library 'bluecloth'
74
+ end
75
+
76
+ def prepare
77
+ @engine = BlueCloth.new(data, options)
78
+ @output = nil
79
+ end
80
+
81
+ def evaluate(scope, locals, &block)
82
+ @output ||= @engine.to_html
83
+ end
84
+ end
85
+
86
+ # Maruku markdown implementation. See:
87
+ # http://maruku.rubyforge.org/
88
+ class MarukuTemplate < Template
89
+ def self.engine_initialized?
90
+ defined? ::Maruku
91
+ end
92
+
93
+ def initialize_engine
94
+ require_template_library 'maruku'
95
+ end
96
+
97
+ def prepare
98
+ @engine = Maruku.new(data, options)
99
+ @output = nil
100
+ end
101
+
102
+ def evaluate(scope, locals, &block)
103
+ @output ||= @engine.to_html
104
+ end
105
+ end
106
+
107
+ # Kramdown Markdown implementation. See:
108
+ # http://kramdown.rubyforge.org/
109
+ class KramdownTemplate < Template
110
+ DUMB_QUOTES = [39, 39, 34, 34]
111
+
112
+ def self.engine_initialized?
113
+ defined? ::Kramdown
114
+ end
115
+
116
+ def initialize_engine
117
+ require_template_library 'kramdown'
118
+ end
119
+
120
+ def prepare
121
+ options[:smart_quotes] = DUMB_QUOTES unless options[:smartypants]
122
+ @engine = Kramdown::Document.new(data, options)
123
+ @output = nil
124
+ end
125
+
126
+ def evaluate(scope, locals, &block)
127
+ @output ||= @engine.to_html
128
+ end
129
+ end
130
+ end
131
+
@@ -0,0 +1,43 @@
1
+ require 'tilt/template'
2
+
3
+ module Tilt
4
+ # Nokogiri template implementation. See:
5
+ # http://nokogiri.org/
6
+ class NokogiriTemplate < Template
7
+ self.default_mime_type = 'text/xml'
8
+
9
+ def self.engine_initialized?
10
+ defined? ::Nokogiri
11
+ end
12
+
13
+ def initialize_engine
14
+ require_template_library 'nokogiri'
15
+ end
16
+
17
+ def prepare; end
18
+
19
+ def evaluate(scope, locals, &block)
20
+ block &&= proc { yield.gsub(/^<\?xml version=\"1\.0\"\?>\n?/, "") }
21
+
22
+ if data.respond_to?(:to_str)
23
+ super(scope, locals, &block)
24
+ else
25
+ ::Nokogiri::XML::Builder.new.tap(&data).to_xml
26
+ end
27
+ end
28
+
29
+ def precompiled_preamble(locals)
30
+ return super if locals.include? :xml
31
+ "xml = ::Nokogiri::XML::Builder.new\n#{super}"
32
+ end
33
+
34
+ def precompiled_postamble(locals)
35
+ "xml.to_xml"
36
+ end
37
+
38
+ def precompiled_template(locals)
39
+ data.to_str
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,51 @@
1
+ require 'tilt/template'
2
+
3
+ module Tilt
4
+ # Radius Template
5
+ # http://github.com/jlong/radius/
6
+ class RadiusTemplate < Template
7
+ def self.engine_initialized?
8
+ defined? ::Radius
9
+ end
10
+
11
+ def self.context_class
12
+ @context_class ||= Class.new(Radius::Context) do
13
+ attr_accessor :tilt_scope
14
+
15
+ def tag_missing(name, attributes)
16
+ tilt_scope.__send__(name)
17
+ end
18
+
19
+ def dup
20
+ i = super
21
+ i.tilt_scope = tilt_scope
22
+ i
23
+ end
24
+ end
25
+ end
26
+
27
+ def initialize_engine
28
+ require_template_library 'radius'
29
+ end
30
+
31
+ def prepare
32
+ end
33
+
34
+ def evaluate(scope, locals, &block)
35
+ context = self.class.context_class.new
36
+ context.tilt_scope = scope
37
+ context.define_tag("yield") do
38
+ block.call
39
+ end
40
+ locals.each do |tag, value|
41
+ context.define_tag(tag) do
42
+ value
43
+ end
44
+ end
45
+
46
+ options = {:tag_prefix => 'r'}.merge(@options)
47
+ parser = Radius::Parser.new(context, options)
48
+ parser.parse(data)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ require 'tilt/template'
2
+
3
+ module Tilt
4
+ # RDoc template. See:
5
+ # http://rdoc.rubyforge.org/
6
+ #
7
+ # It's suggested that your program require 'rdoc/markup' and
8
+ # 'rdoc/markup/to_html' at load time when using this template
9
+ # engine.
10
+ class RDocTemplate < Template
11
+ self.default_mime_type = 'text/html'
12
+
13
+ def self.engine_initialized?
14
+ defined? ::RDoc::Markup
15
+ end
16
+
17
+ def initialize_engine
18
+ require_template_library 'rdoc/markup'
19
+ require_template_library 'rdoc/markup/to_html'
20
+ end
21
+
22
+ def prepare
23
+ markup = RDoc::Markup::ToHtml.new
24
+ @engine = markup.convert(data)
25
+ @output = nil
26
+ end
27
+
28
+ def evaluate(scope, locals, &block)
29
+ @output ||= @engine.to_s
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ require 'tilt/template'
2
+
3
+ module Tilt
4
+ # The template source is evaluated as a Ruby string. The #{} interpolation
5
+ # syntax can be used to generated dynamic output.
6
+ class StringTemplate < Template
7
+ def prepare
8
+ hash = "TILT#{data.hash.abs}"
9
+ @code = "<<#{hash}.chomp\n#{data}\n#{hash}"
10
+ end
11
+
12
+ def precompiled_template(locals)
13
+ @code
14
+ end
15
+
16
+ def precompiled(locals)
17
+ source, offset = super
18
+ [source, offset + 1]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,285 @@
1
+ module Tilt
2
+ TOPOBJECT = defined?(BasicObject) ? BasicObject : Object
3
+
4
+ # Base class for template implementations. Subclasses must implement
5
+ # the #prepare method and one of the #evaluate or #precompiled_template
6
+ # methods.
7
+ class Template
8
+ # Template source; loaded from a file or given directly.
9
+ attr_reader :data
10
+
11
+ # The name of the file where the template data was loaded from.
12
+ attr_reader :file
13
+
14
+ # The line number in #file where template data was loaded from.
15
+ attr_reader :line
16
+
17
+ # A Hash of template engine specific options. This is passed directly
18
+ # to the underlying engine and is not used by the generic template
19
+ # interface.
20
+ attr_reader :options
21
+
22
+ # Used to determine if this class's initialize_engine method has
23
+ # been called yet.
24
+ @engine_initialized = false
25
+ class << self
26
+ attr_accessor :engine_initialized
27
+ alias engine_initialized? engine_initialized
28
+
29
+ attr_accessor :default_mime_type
30
+ end
31
+
32
+ # Create a new template with the file, line, and options specified. By
33
+ # default, template data is read from the file. When a block is given,
34
+ # it should read template data and return as a String. When file is nil,
35
+ # a block is required.
36
+ #
37
+ # All arguments are optional.
38
+ def initialize(file=nil, line=1, options={}, &block)
39
+ @file, @line, @options = nil, 1, {}
40
+
41
+ [options, line, file].compact.each do |arg|
42
+ case
43
+ when arg.respond_to?(:to_str) ; @file = arg.to_str
44
+ when arg.respond_to?(:to_int) ; @line = arg.to_int
45
+ when arg.respond_to?(:to_hash) ; @options = arg.to_hash.dup
46
+ else raise TypeError
47
+ end
48
+ end
49
+
50
+ raise ArgumentError, "file or block required" if (@file || block).nil?
51
+
52
+ # call the initialize_engine method if this is the very first time
53
+ # an instance of this class has been created.
54
+ if !self.class.engine_initialized?
55
+ initialize_engine
56
+ self.class.engine_initialized = true
57
+ end
58
+
59
+ # used to hold compiled template methods
60
+ @compiled_method = {}
61
+
62
+ # used on 1.9 to set the encoding if it is not set elsewhere (like a magic comment)
63
+ # currently only used if template compiles to ruby
64
+ @default_encoding = @options.delete :default_encoding
65
+
66
+ # load template data and prepare (uses binread to avoid encoding issues)
67
+ @reader = block || lambda { |t| File.respond_to?(:binread) ? File.binread(@file) : File.read(@file) }
68
+ @data = @reader.call(self)
69
+ prepare
70
+ end
71
+
72
+ # Render the template in the given scope with the locals specified. If a
73
+ # block is given, it is typically available within the template via
74
+ # +yield+.
75
+ def render(scope=Object.new, locals={}, &block)
76
+ evaluate scope, locals || {}, &block
77
+ end
78
+
79
+ # The basename of the template file.
80
+ def basename(suffix='')
81
+ File.basename(file, suffix) if file
82
+ end
83
+
84
+ # The template file's basename with all extensions chomped off.
85
+ def name
86
+ basename.split('.', 2).first if basename
87
+ end
88
+
89
+ # The filename used in backtraces to describe the template.
90
+ def eval_file
91
+ file || '(__TEMPLATE__)'
92
+ end
93
+
94
+ protected
95
+ # Called once and only once for each template subclass the first time
96
+ # the template class is initialized. This should be used to require the
97
+ # underlying template library and perform any initial setup.
98
+ def initialize_engine
99
+ end
100
+
101
+ # Like Kernel::require but issues a warning urging a manual require when
102
+ # running under a threaded environment.
103
+ def require_template_library(name)
104
+ if Thread.list.size > 1
105
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
106
+ "explicit require '#{name}' suggested."
107
+ end
108
+ require name
109
+ end
110
+
111
+ # Do whatever preparation is necessary to setup the underlying template
112
+ # engine. Called immediately after template data is loaded. Instance
113
+ # variables set in this method are available when #evaluate is called.
114
+ #
115
+ # Subclasses must provide an implementation of this method.
116
+ def prepare
117
+ if respond_to?(:compile!)
118
+ # backward compat with tilt < 0.6; just in case
119
+ warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
120
+ compile!
121
+ else
122
+ raise NotImplementedError
123
+ end
124
+ end
125
+
126
+ def evaluate(scope, locals, &block)
127
+ cached_evaluate(scope, locals, &block)
128
+ end
129
+
130
+ # Process the template and return the result. The first time this
131
+ # method is called, the template source is evaluated with instance_eval.
132
+ # On the sequential method calls it will compile the template to an
133
+ # unbound method which will lead to better performance. In any case,
134
+ # template executation is guaranteed to be performed in the scope object
135
+ # with the locals specified and with support for yielding to the block.
136
+ def cached_evaluate(scope, locals, &block)
137
+ # Redefine itself to use method compilation the next time:
138
+ def self.cached_evaluate(scope, locals, &block)
139
+ method = compiled_method(locals.keys)
140
+ method.bind(scope).call(locals, &block)
141
+ end
142
+
143
+ # Use instance_eval the first time:
144
+ evaluate_source(scope, locals, &block)
145
+ end
146
+
147
+ # Generates all template source by combining the preamble, template, and
148
+ # postamble and returns a two-tuple of the form: [source, offset], where
149
+ # source is the string containing (Ruby) source code for the template and
150
+ # offset is the integer line offset where line reporting should begin.
151
+ #
152
+ # Template subclasses may override this method when they need complete
153
+ # control over source generation or want to adjust the default line
154
+ # offset. In most cases, overriding the #precompiled_template method is
155
+ # easier and more appropriate.
156
+ def precompiled(locals)
157
+ preamble = precompiled_preamble(locals)
158
+ 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
164
+ end
165
+ parts = [
166
+ preamble,
167
+ template,
168
+ precompiled_postamble(locals)
169
+ ]
170
+ [parts.join("\n"), preamble.count("\n") + 1]
171
+ end
172
+
173
+ # 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.
179
+ def precompiled_template(locals)
180
+ raise NotImplementedError
181
+ end
182
+
183
+ # Generates preamble code for initializing template state, and performing
184
+ # locals assignment. The default implementation performs locals
185
+ # assignment only. Lines included in the preamble are subtracted from the
186
+ # source line offset, so adding code to the preamble does not effect line
187
+ # reporting in Kernel::caller and backtraces.
188
+ def precompiled_preamble(locals)
189
+ locals.map { |k,v| "#{k} = locals[#{k.inspect}]" }.join("\n")
190
+ end
191
+
192
+ # Generates postamble code for the precompiled template source. The
193
+ # string returned from this method is appended to the precompiled
194
+ # template source.
195
+ def precompiled_postamble(locals)
196
+ ''
197
+ end
198
+
199
+ # The compiled method for the locals keys provided.
200
+ def compiled_method(locals_keys)
201
+ @compiled_method[locals_keys] ||=
202
+ compile_template_method(locals_keys)
203
+ end
204
+
205
+ private
206
+ # Evaluate the template source in the context of the scope object.
207
+ def evaluate_source(scope, locals, &block)
208
+ source, offset = precompiled(locals)
209
+ scope.instance_eval(source, eval_file, line - offset)
210
+ end
211
+
212
+ # JRuby doesn't allow Object#instance_eval to yield to the block it's
213
+ # closed over. This is by design and (ostensibly) something that will
214
+ # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
215
+ # exhibits the behavior. More info here:
216
+ #
217
+ # http://jira.codehaus.org/browse/JRUBY-2599
218
+ #
219
+ # We redefine evaluate_source to work around this issues.
220
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
221
+ undef evaluate_source
222
+ def evaluate_source(scope, locals, &block)
223
+ source, offset = precompiled(locals)
224
+ file, lineno = eval_file, (line - offset)
225
+ scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
226
+ end
227
+ end
228
+
229
+ def compile_template_method(locals)
230
+ source, offset = precompiled(locals)
231
+ offset += 5
232
+ method_name = "__tilt_#{Thread.current.object_id.abs}"
233
+ Object.class_eval <<-RUBY, eval_file, line - offset
234
+ #{extract_magic_comment source}
235
+ TOPOBJECT.class_eval do
236
+ def #{method_name}(locals)
237
+ Thread.current[:tilt_vars] = [self, locals]
238
+ class << self
239
+ this, locals = Thread.current[:tilt_vars]
240
+ this.instance_eval do
241
+ #{source}
242
+ end
243
+ end
244
+ end
245
+ end
246
+ RUBY
247
+ unbind_compiled_method(method_name)
248
+ end
249
+
250
+ def unbind_compiled_method(method_name)
251
+ method = TOPOBJECT.instance_method(method_name)
252
+ TOPOBJECT.class_eval { remove_method(method_name) }
253
+ method
254
+ end
255
+
256
+ def extract_magic_comment(script)
257
+ comment = script.slice(/\A[ \t]*\#.*coding\s*[=:]\s*([[:alnum:]\-_]+).*$/)
258
+ return comment if comment and not %w[ascii-8bit binary].include?($1.downcase)
259
+ "# coding: #{@default_encoding}" if @default_encoding
260
+ end
261
+
262
+ # Special case Ruby 1.9.1's broken yield.
263
+ #
264
+ # http://github.com/rtomayko/tilt/commit/20c01a5
265
+ # http://redmine.ruby-lang.org/issues/show/3601
266
+ #
267
+ # Remove when 1.9.2 dominates 1.9.1 installs in the wild.
268
+ if RUBY_VERSION =~ /^1.9.1/
269
+ undef compile_template_method
270
+ def compile_template_method(locals)
271
+ source, offset = precompiled(locals)
272
+ offset += 1
273
+ method_name = "__tilt_#{Thread.current.object_id}"
274
+ Object.class_eval <<-RUBY, eval_file, line - offset
275
+ TOPOBJECT.class_eval do
276
+ def #{method_name}(locals)
277
+ #{source}
278
+ end
279
+ end
280
+ RUBY
281
+ unbind_compiled_method(method_name)
282
+ end
283
+ end
284
+ end
285
+ end