sinatra 1.0.a → 1.0.b

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

data/CHANGES CHANGED
@@ -1,58 +1,88 @@
1
- = 1.0 / unreleased
1
+ = 1.0 / 2010-01-28 (prerelease)
2
2
 
3
3
  * It's now possible to register blocks to run after each request using
4
4
  after filters. After filters run at the end of each request, after
5
- routes and error handlers.
5
+ routes and error handlers. (Jimmy Schementi)
6
6
 
7
7
  * Sinatra now uses Tilt <http://github.com/rtomayko/tilt> for rendering
8
8
  templates. This adds support for template caching, consistent
9
9
  template backtraces, and support for new template engines, like
10
- mustache and liquid.
10
+ mustache and liquid. (Ryan Tomayko)
11
+
12
+ * ERB, Erubis, and Haml templates are now compiled the first time
13
+ they're rendered instead of being string eval'd on each invocation.
14
+ Benchmarks show a 5x-10x improvement in render time. This also
15
+ reduces the number of objects created, decreasing pressure on Ruby's
16
+ GC. (Ryan Tomayko)
17
+
18
+ * New 'settings' method gives access to options in both class and request
19
+ scopes. This replaces the 'options' method. (Chris Wanstrath)
11
20
 
12
21
  * New boolean 'reload_templates' setting controls whether template files
13
22
  are reread from disk and recompiled on each request. Template read/compile
14
- is cached by default in all environments except development.
23
+ is cached by default in all environments except development. (Ryan Tomayko)
15
24
 
16
- * New 'settings' method gives access to options in both class and request
17
- scopes. This replaces the 'options' method.
18
- * New 'erubis' helper method for rendering Erubis templates.
25
+ * New 'erubis' helper method for rendering ERB template with Erubis. The
26
+ erubis gem is required. (Dylan Egan)
19
27
 
20
28
  * New 'cache_control' helper method provides a convenient way of
21
29
  setting the Cache-Control response header. Takes a variable number
22
30
  of boolean directives followed by a hash of value directives, like
23
- this:
24
- cache_control :public, :must_revalidate, :max_age => 60
31
+ this: cache_control :public, :must_revalidate, :max_age => 60
32
+ (Ryan Tomayko)
25
33
 
26
34
  * New 'expires' helper method is like cache_control but takes an
27
35
  integer number of seconds or Time object:
28
- expires 300, :public, :must_revalidate
36
+ expires 300, :public, :must_revalidate
37
+ (Ryan Tomayko)
38
+
39
+ * New request.secure? method for checking for an SSL connection.
40
+ (Adam Wiggins)
29
41
 
30
- * Sinatra apps can now be run with a `-h <addr>` argument to specify
31
- the address to bind to.
42
+ * Sinatra apps can now be run with a `-o <addr>` argument to specify
43
+ the address to bind to. (Ryan Tomayko)
32
44
 
33
45
  * Rack::Session::Cookie is now added to the middleware pipeline when
34
46
  running in test environments if the :sessions option is set.
47
+ (Simon Rozet)
35
48
 
36
49
  * Route handlers, before filters, templates, error mappings, and
37
50
  middleware are now resolved dynamically up the inheritance hierarchy
38
51
  when needed instead of duplicating the superclass's version when
39
52
  a new Sinatra::Base subclass is created. This should fix a variety
40
53
  of issues with extensions that need to add any of these things
41
- to the base class.
54
+ to the base class. (Ryan Tomayko)
55
+
56
+ * Exception error handlers always override the raise_errors option now.
57
+ Previously, all exceptions would be raised outside of the application
58
+ when the raise_errors option was enabled, even if an error handler was
59
+ defined for that exception. The raise_errors option now controls
60
+ whether unhandled exceptions are raised (enabled) or if a generic 500
61
+ error is returned (disabled). (Ryan Tomayko)
42
62
 
43
- The following Sinatra features have been obsoleted in the 1.0 release:
63
+ * The X-Cascade response header is set to 'pass' when no matching route
64
+ is found or all routes pass. (Josh Peek)
44
65
 
45
- * The `sinatra/test` library is obsolete. This includes the
46
- `Sinatra::Test` module, the `Sinatra::TestHarness` class,
47
- and the `get_it`, `post_it`, `put_it`, `delete_it`, and `head_it`
48
- helper methods. The
66
+ * Filters do not run when serving static files anymore. (Ryan Tomayko)
67
+
68
+ The following Sinatra features have been obsoleted (removed entirely) in
69
+ the 1.0 release:
70
+
71
+ * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test`
72
+ module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`,
73
+ `put_it`, `delete_it`, and `head_it` helper methods. The
49
74
  [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should
50
75
  be used instead.
51
76
 
52
77
  * Test framework specific libraries (`sinatra/test/spec`,
53
- `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete.
54
- See http://www.sinatrarb.com/testing.html for instructions on
55
- setting up a testing environment under each of these frameworks.
78
+ `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See
79
+ http://www.sinatrarb.com/testing.html for instructions on setting up a
80
+ testing environment under each of these frameworks.
81
+
82
+ * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead.
83
+ `Sinatra::Base` acts more like `Sinatra::Default` in development mode.
84
+ For example, static file serving and sexy development error pages are
85
+ enabled by default.
56
86
 
57
87
  * Auto-requiring template libraries in the `erb`, `builder`, `haml`,
58
88
  and `sass` methods is obsolete due to thread-safety issues. You must
@@ -65,8 +95,13 @@ The following Sinatra features have been obsoleted in the 1.0 release:
65
95
  Template engine options should be passed in the second Hash argument
66
96
  instead.
67
97
 
98
+ * The `use_in_file_templates` method is obsolete. Use
99
+ `enable :inline_templates` or `set :inline_templates, 'path/to/file'`
100
+
68
101
  * The 'media_type' helper method is obsolete. Use 'mime_type' instead.
69
102
 
103
+ * The 'mime' main and class method is obsolete. Use 'mime_type' instead.
104
+
70
105
  * The request-level `send_data` method is no longer supported.
71
106
 
72
107
  * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer
@@ -100,6 +135,9 @@ The following Sinatra features have been obsoleted in the 1.0 release:
100
135
  within a request are now treated as internal server errors and result in
101
136
  a 500 response status.
102
137
 
138
+ * The `:methodoverride' option to enable/disable the POST _method hack is
139
+ obsolete; use `:method_override` instead.
140
+
103
141
  = 0.9.2 / 2009-05-18
104
142
 
105
143
  * This version is compatible with Rack 1.0. [Rein Henrichs]
@@ -203,6 +203,20 @@ and overridden on an individual basis.
203
203
  sass :stylesheet, :style => :expanded # overridden
204
204
  end
205
205
 
206
+ === Less Templates
207
+
208
+ The less gem/library is required to render Less templates:
209
+
210
+ ## You'll need to require less in your app
211
+ require 'less'
212
+
213
+ get '/stylesheet.css' do
214
+ content_type 'text/css', :charset => 'utf-8'
215
+ less :stylesheet
216
+ end
217
+
218
+ Renders <tt>./views/stylesheet.less</tt>.
219
+
206
220
  === Inline Templates
207
221
 
208
222
  get '/' do
@@ -565,20 +579,20 @@ system. The +Sinatra::Application+ class -- a special subclass of
565
579
  Sinatra::Base -- receives all :get, :put, :post, :delete, :before,
566
580
  :error, :not_found, :configure, and :set messages sent to the
567
581
  top-level. Have a look at the code for yourself: here's the
568
- {Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064]
569
- being {included into the main namespace}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25].
582
+ {Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128]
583
+ being {included into the main namespace}[http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28]
570
584
 
571
585
  == Command line
572
586
 
573
587
  Sinatra applications can be run directly:
574
588
 
575
- ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER]
589
+ ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
576
590
 
577
591
  Options are:
578
592
 
579
593
  -h # help
580
594
  -p # set the port (default is 4567)
581
- -h # set the host (default is 0.0.0.0)
595
+ -o # set the host (default is 0.0.0.0)
582
596
  -e # set the environment (default is development)
583
597
  -s # specify rack server/handler (default is thin)
584
598
  -x # turn on the mutex lock (default is off)
@@ -611,12 +625,12 @@ To update the Sinatra sources in the future:
611
625
 
612
626
  == More
613
627
 
614
- * {Project Website}[http://sinatra.github.com/] - Additional documentation,
628
+ * {Project Website}[http://www.sinatrarb.com/] - Additional documentation,
615
629
  news, and links to other resources.
616
- * {Contributing}[http://sinatra.github.com/contributing.html] - Find a bug? Need
630
+ * {Contributing}[http://www.sinatrarb.com/contributing] - Find a bug? Need
617
631
  help? Have a patch?
618
632
  * {Lighthouse}[http://sinatra.lighthouseapp.com] - Issue tracking and release
619
633
  planning.
620
634
  * {Twitter}[http://twitter.com/sinatra]
621
- * {Mailing List}[http://groups.google.com/group/sinatrarb]
635
+ * {Mailing List}[http://groups.google.com/group/sinatrarb/topics]
622
636
  * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
data/Rakefile CHANGED
@@ -37,16 +37,19 @@ task 'doc' => ['doc:api']
37
37
  task 'doc:api' => ['doc/api/index.html']
38
38
 
39
39
  file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f|
40
+ require 'rbconfig'
41
+ hanna = RbConfig::CONFIG['ruby_install_name'].sub('ruby', 'hanna')
40
42
  rb_files = f.prerequisites
41
43
  sh((<<-end).gsub(/\s+/, ' '))
42
- hanna --charset utf8 \
43
- --fmt html \
44
- --inline-source \
45
- --line-numbers \
46
- --main README.rdoc \
47
- --op doc/api \
48
- --title 'Sinatra API Documentation' \
49
- #{rb_files.join(' ')}
44
+ #{hanna}
45
+ --charset utf8
46
+ --fmt html
47
+ --inline-source
48
+ --line-numbers
49
+ --main README.rdoc
50
+ --op doc/api
51
+ --title 'Sinatra API Documentation'
52
+ #{rb_files.join(' ')}
50
53
  end
51
54
  end
52
55
  CLEAN.include 'doc/api'
@@ -8,12 +8,18 @@ require 'sinatra/showexceptions'
8
8
  # require tilt if available; fall back on bundled version.
9
9
  begin
10
10
  require 'tilt'
11
+ if Tilt::VERSION < '0.8'
12
+ warn "WARN: sinatra requires tilt >= 0.8; you have #{Tilt::VERSION}. " +
13
+ "loading bundled version..."
14
+ Object.send :remove_const, :Tilt
15
+ raise LoadError
16
+ end
11
17
  rescue LoadError
12
18
  require 'sinatra/tilt'
13
19
  end
14
20
 
15
21
  module Sinatra
16
- VERSION = '1.0.a'
22
+ VERSION = '1.0.b'
17
23
 
18
24
  # The request object. See Rack::Request for more info:
19
25
  # http://rack.rubyforge.org/doc/classes/Rack/Request.html
@@ -240,6 +246,7 @@ module Sinatra
240
246
  # matches the time specified, execution is immediately halted with a
241
247
  # '304 Not Modified' response.
242
248
  def last_modified(time)
249
+ return unless time
243
250
  time = time.to_time if time.respond_to?(:to_time)
244
251
  time = time.httpdate if time.respond_to?(:httpdate)
245
252
  response['Last-Modified'] = time
@@ -284,15 +291,19 @@ module Sinatra
284
291
  #
285
292
  # Possible options are:
286
293
  # :layout If set to false, no layout is rendered, otherwise
287
- # the specified layout is used (Ignored for `sass`)
294
+ # the specified layout is used (Ignored for `sass` and `less`)
288
295
  # :locals A hash with local variables that should be available
289
296
  # in the template
290
297
  module Templates
298
+ include Tilt::CompileSite
299
+
291
300
  def erb(template, options={}, locals={})
301
+ options[:outvar] = '@_out_buf'
292
302
  render :erb, template, options, locals
293
303
  end
294
304
 
295
305
  def erubis(template, options={}, locals={})
306
+ options[:outvar] = '@_out_buf'
296
307
  render :erubis, template, options, locals
297
308
  end
298
309
 
@@ -305,6 +316,11 @@ module Sinatra
305
316
  render :sass, template, options, locals
306
317
  end
307
318
 
319
+ def less(template, options={}, locals={})
320
+ options[:layout] = false
321
+ render :less, template, options, locals
322
+ end
323
+
308
324
  def builder(template=nil, options={}, locals={}, &block)
309
325
  options, template = template, nil if template.is_a?(Hash)
310
326
  template = Proc.new { block } if template.nil?
@@ -340,20 +356,23 @@ module Sinatra
340
356
 
341
357
  def compile_template(engine, data, options, views)
342
358
  @template_cache.fetch engine, data, options do
359
+ template = Tilt[engine]
360
+ raise "Template engine not found: #{engine}" if template.nil?
361
+
343
362
  case
344
363
  when data.is_a?(Symbol)
345
364
  body, path, line = self.class.templates[data]
346
365
  if body
347
366
  body = body.call if body.respond_to?(:call)
348
- Tilt[engine].new(path, line.to_i, options) { body }
367
+ template.new(path, line.to_i, options) { body }
349
368
  else
350
369
  path = ::File.join(views, "#{data}.#{engine}")
351
- Tilt[engine].new(path, 1, options)
370
+ template.new(path, 1, options)
352
371
  end
353
372
  when data.is_a?(Proc) || data.is_a?(String)
354
373
  body = data.is_a?(String) ? Proc.new { data } : data
355
374
  path, line = self.class.caller_locations.first
356
- Tilt[engine].new(path, line.to_i, options, &body)
375
+ template.new(path, line.to_i, options, &body)
357
376
  else
358
377
  raise ArgumentError
359
378
  end
@@ -600,10 +619,16 @@ module Sinatra
600
619
  @env['sinatra.error'] = boom
601
620
 
602
621
  dump_errors!(boom) if settings.dump_errors?
603
- raise boom if settings.raise_errors? || settings.show_exceptions?
622
+ raise boom if settings.show_exceptions?
604
623
 
605
624
  @response.status = 500
606
- error_block! boom.class, Exception
625
+ if res = error_block!(boom.class)
626
+ res
627
+ elsif settings.raise_errors?
628
+ raise boom
629
+ else
630
+ error_block!(Exception)
631
+ end
607
632
  end
608
633
 
609
634
  # Find an custom error block for the key(s) specified.
@@ -613,8 +638,7 @@ module Sinatra
613
638
  while base.respond_to?(:errors)
614
639
  if block = base.errors[key]
615
640
  # found a handler, eval and return result
616
- res = instance_eval(&block)
617
- return res
641
+ return instance_eval(&block)
618
642
  else
619
643
  base = base.superclass
620
644
  end
@@ -679,11 +703,13 @@ module Sinatra
679
703
 
680
704
  # Sets an option to the given value. If the value is a proc,
681
705
  # the proc will be called every time the option is accessed.
682
- def set(option, value=self)
706
+ def set(option, value=self, &block)
707
+ raise ArgumentError if block && value != self
708
+ value = block if block
683
709
  if value.kind_of?(Proc)
684
710
  metadef(option, &value)
685
711
  metadef("#{option}?") { !!__send__(option) }
686
- metadef("#{option}=") { |val| set(option, Proc.new{val}) }
712
+ metadef("#{option}=") { |val| metadef(option, &Proc.new{val}) }
687
713
  elsif value == self && option.respond_to?(:to_hash)
688
714
  option.to_hash.each { |k,v| set(k, v) }
689
715
  elsif respond_to?("#{option}=")
@@ -833,7 +859,7 @@ module Sinatra
833
859
  private
834
860
  def route(verb, path, options={}, &block)
835
861
  # Because of self.options.host
836
- host_name(options.delete(:host)) if options.key?(:host)
862
+ host_name(options.delete(:bind)) if options.key?(:host)
837
863
 
838
864
  options.each {|option, args| send(option, *args)}
839
865
 
@@ -927,7 +953,7 @@ module Sinatra
927
953
  handler_name = handler.name.gsub(/.*::/, '')
928
954
  puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
929
955
  "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
930
- handler.run self, :Host => host, :Port => port do |server|
956
+ handler.run self, :Host => bind, :Port => port do |server|
931
957
  trap(:INT) do
932
958
  ## Use thins' hard #stop! if available, otherwise just #stop
933
959
  server.respond_to?(:stop!) ? server.stop! : server.stop
@@ -951,7 +977,7 @@ module Sinatra
951
977
  builder = Rack::Builder.new
952
978
  builder.use Rack::Session::Cookie if sessions?
953
979
  builder.use Rack::CommonLogger if logging?
954
- builder.use Rack::MethodOverride if methodoverride?
980
+ builder.use Rack::MethodOverride if method_override?
955
981
  builder.use ShowExceptions if show_exceptions?
956
982
  middleware.each { |c,a,b| builder.use(c, *a, &b) }
957
983
 
@@ -1024,24 +1050,29 @@ module Sinatra
1024
1050
  reset!
1025
1051
 
1026
1052
  set :environment, (ENV['RACK_ENV'] || :development).to_sym
1027
- set :raise_errors, Proc.new { !development? }
1028
- set :dump_errors, Proc.new { development? }
1053
+ set :raise_errors, Proc.new { test? }
1054
+ set :dump_errors, Proc.new { !test? }
1029
1055
  set :show_exceptions, Proc.new { development? }
1030
1056
  set :clean_trace, true
1031
1057
  set :sessions, false
1032
1058
  set :logging, false
1033
- set :methodoverride, false
1059
+ set :method_override, false
1060
+
1061
+ class << self
1062
+ alias_method :methodoverride?, :method_override?
1063
+ alias_method :methodoverride=, :method_override=
1064
+ end
1034
1065
 
1035
1066
  set :run, false # start server via at-exit hook?
1036
1067
  set :running, false # is the built-in server running now?
1037
1068
  set :server, %w[thin mongrel webrick]
1038
- set :host, '0.0.0.0'
1069
+ set :bind, '0.0.0.0'
1039
1070
  set :port, 4567
1040
1071
 
1041
1072
  set :app_file, nil
1042
1073
  set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1043
1074
  set :views, Proc.new { root && File.join(root, 'views') }
1044
- set :reload_templates, Proc.new { !development? }
1075
+ set :reload_templates, Proc.new { development? }
1045
1076
  set :lock, false
1046
1077
 
1047
1078
  set :public, Proc.new { root && File.join(root, 'public') }
@@ -1095,13 +1126,9 @@ module Sinatra
1095
1126
  # top-level. Subclassing Sinatra::Base is heavily recommended for
1096
1127
  # modular applications.
1097
1128
  class Application < Base
1098
- set :raise_errors, Proc.new { test? }
1099
- set :dump_errors, true
1100
- set :sessions, false
1101
1129
  set :logging, Proc.new { ! test? }
1102
- set :methodoverride, true
1130
+ set :method_override, true
1103
1131
  set :run, Proc.new { ! test? }
1104
- set :static, true
1105
1132
 
1106
1133
  def self.register(*extensions, &block) #:nodoc:
1107
1134
  added_methods = extensions.map {|m| m.public_instance_methods }.flatten
@@ -17,7 +17,7 @@ module Sinatra
17
17
  op.on('-e env') { |val| set :environment, val.to_sym }
18
18
  op.on('-s server') { |val| set :server, val }
19
19
  op.on('-p port') { |val| set :port, val.to_i }
20
- op.on('-h addr') { |val| set :host, val }
20
+ op.on('-o addr') { |val| set :bind, val }
21
21
  }.parse!(ARGV.dup)
22
22
  end
23
23
  end
@@ -1,10 +1,11 @@
1
+ require 'digest/md5'
2
+
1
3
  module Tilt
2
- VERSION = '0.4'
4
+ VERSION = '0.8'
3
5
 
4
6
  @template_mappings = {}
5
7
 
6
- # Hash of template path pattern => template implementation
7
- # class mappings.
8
+ # Hash of template path pattern => template implementation class mappings.
8
9
  def self.mappings
9
10
  @template_mappings
10
11
  end
@@ -25,7 +26,7 @@ module Tilt
25
26
  end
26
27
  end
27
28
 
28
- # Lookup a template class given for the given filename or file
29
+ # Lookup a template class for the given filename or file
29
30
  # extension. Return nil when no implementation is found.
30
31
  def self.[](file)
31
32
  if @template_mappings.key?(pattern = file.to_s.downcase)
@@ -44,9 +45,24 @@ module Tilt
44
45
  end
45
46
  end
46
47
 
48
+ # Mixin allowing template compilation on scope objects.
49
+ #
50
+ # Including this module in scope objects passed to Template#render
51
+ # causes template source to be compiled to methods the first time they're
52
+ # used. This can yield significant (5x-10x) performance increases for
53
+ # templates that support it (ERB, Erubis, Builder).
54
+ #
55
+ # It's also possible (though not recommended) to include this module in
56
+ # Object to enable template compilation globally. The downside is that
57
+ # the template methods will polute the global namespace and could lead to
58
+ # unexpected behavior.
59
+ module CompileSite
60
+ def __tilt__
61
+ end
62
+ end
47
63
 
48
64
  # Base class for template implementations. Subclasses must implement
49
- # the #compile! method and one of the #evaluate or #template_source
65
+ # the #prepare method and one of the #evaluate or #template_source
50
66
  # methods.
51
67
  class Template
52
68
  # Template source; loaded from a file or given directly.
@@ -63,51 +79,55 @@ module Tilt
63
79
  # interface.
64
80
  attr_reader :options
65
81
 
82
+ # Used to determine if this class's initialize_engine method has
83
+ # been called yet.
84
+ @engine_initialized = false
85
+ class << self
86
+ attr_accessor :engine_initialized
87
+ alias engine_initialized? engine_initialized
88
+ end
89
+
66
90
  # Create a new template with the file, line, and options specified. By
67
- # default, template data is read from the file specified. When a block
68
- # is given, it should read template data and return as a String. When
69
- # file is nil, a block is required.
91
+ # default, template data is read from the file. When a block is given,
92
+ # it should read template data and return as a String. When file is nil,
93
+ # a block is required.
70
94
  #
71
- # The #initialize_engine method is called if this is the very first
72
- # time this template subclass has been initialized.
95
+ # All arguments are optional.
73
96
  def initialize(file=nil, line=1, options={}, &block)
74
- raise ArgumentError, "file or block required" if file.nil? && block.nil?
75
- options, line = line, 1 if line.is_a?(Hash)
76
- @file = file
77
- @line = line || 1
78
- @options = options || {}
79
- @reader = block || lambda { |t| File.read(file) }
80
-
81
- if !self.class.engine_initialized
97
+ @file, @line, @options = nil, 1, {}
98
+
99
+ [options, line, file].compact.each do |arg|
100
+ case
101
+ when arg.respond_to?(:to_str) ; @file = arg.to_str
102
+ when arg.respond_to?(:to_int) ; @line = arg.to_int
103
+ when arg.respond_to?(:to_hash) ; @options = arg.to_hash
104
+ else raise TypeError
105
+ end
106
+ end
107
+
108
+ raise ArgumentError, "file or block required" if (@file || block).nil?
109
+
110
+ # call the initialize_engine method if this is the very first time
111
+ # an instance of this class has been created.
112
+ if !self.class.engine_initialized?
82
113
  initialize_engine
83
114
  self.class.engine_initialized = true
84
115
  end
85
- end
86
-
87
- # Called once and only once for each template subclass the first time
88
- # the template class is initialized. This should be used to require the
89
- # underlying template library and perform any initial setup.
90
- def initialize_engine
91
- end
92
- @engine_initialized = false
93
- class << self ; attr_accessor :engine_initialized ; end
94
116
 
117
+ # used to generate unique method names for template compilation
118
+ @stamp = (Time.now.to_f * 10000).to_i
119
+ @compiled_method_names = {}
95
120
 
96
- # Load template source and compile the template. The template is
97
- # loaded and compiled the first time this method is called; subsequent
98
- # calls are no-ops.
99
- def compile
100
- if @data.nil?
101
- @data = @reader.call(self)
102
- compile!
103
- end
121
+ # load template data and prepare
122
+ @reader = block || lambda { |t| File.read(@file) }
123
+ @data = @reader.call(self)
124
+ prepare
104
125
  end
105
126
 
106
127
  # Render the template in the given scope with the locals specified. If a
107
128
  # block is given, it is typically available within the template via
108
129
  # +yield+.
109
130
  def render(scope=Object.new, locals={}, &block)
110
- compile
111
131
  evaluate scope, locals || {}, &block
112
132
  end
113
133
 
@@ -127,43 +147,167 @@ module Tilt
127
147
  end
128
148
 
129
149
  protected
130
- # Do whatever preparation is necessary to "compile" the template.
131
- # Called immediately after template #data is loaded. Instance variables
132
- # set in this method are available when #evaluate is called.
150
+ # Called once and only once for each template subclass the first time
151
+ # the template class is initialized. This should be used to require the
152
+ # underlying template library and perform any initial setup.
153
+ def initialize_engine
154
+ end
155
+
156
+ # Like Kernel::require but issues a warning urging a manual require when
157
+ # running under a threaded environment.
158
+ def require_template_library(name)
159
+ if Thread.list.size > 1
160
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
161
+ "explicit require '#{name}' suggested."
162
+ end
163
+ require name
164
+ end
165
+
166
+ # Do whatever preparation is necessary to setup the underlying template
167
+ # engine. Called immediately after template data is loaded. Instance
168
+ # variables set in this method are available when #evaluate is called.
133
169
  #
134
170
  # Subclasses must provide an implementation of this method.
135
- def compile!
136
- raise NotImplementedError
171
+ def prepare
172
+ if respond_to?(:compile!)
173
+ # backward compat with tilt < 0.6; just in case
174
+ warn 'Tilt::Template#compile! is deprecated; implement #prepare instead.'
175
+ compile!
176
+ else
177
+ raise NotImplementedError
178
+ end
137
179
  end
138
180
 
139
- # Process the template and return the result. Subclasses should override
140
- # this method unless they implement the #template_source.
181
+ # Process the template and return the result. When the scope mixes in
182
+ # the Tilt::CompileSite module, the template is compiled to a method and
183
+ # reused given identical locals keys. When the scope object
184
+ # does not mix in the CompileSite module, the template source is
185
+ # evaluated with instance_eval. In any case, template executation
186
+ # is guaranteed to be performed in the scope object with the locals
187
+ # specified and with support for yielding to the block.
141
188
  def evaluate(scope, locals, &block)
142
- source, offset = local_assignment_code(locals)
143
- source = [source, template_source].join("\n")
144
- scope.instance_eval source, eval_file, line - offset
189
+ if scope.respond_to?(:__tilt__)
190
+ method_name = compiled_method_name(locals.keys)
191
+ if scope.respond_to?(method_name)
192
+ scope.send(method_name, locals, &block)
193
+ else
194
+ compile_template_method(method_name, locals)
195
+ scope.send(method_name, locals, &block)
196
+ end
197
+ else
198
+ evaluate_source(scope, locals, &block)
199
+ end
145
200
  end
146
201
 
147
- # Return a string containing the (Ruby) source code for the template. The
148
- # default Template#evaluate implementation requires this method be
149
- # defined.
150
- def template_source
202
+ # Generates all template source by combining the preamble, template, and
203
+ # postamble and returns a two-tuple of the form: [source, offset], where
204
+ # source is the string containing (Ruby) source code for the template and
205
+ # offset is the integer line offset where line reporting should begin.
206
+ #
207
+ # Template subclasses may override this method when they need complete
208
+ # control over source generation or want to adjust the default line
209
+ # offset. In most cases, overriding the #precompiled_template method is
210
+ # easier and more appropriate.
211
+ def precompiled(locals)
212
+ preamble = precompiled_preamble(locals)
213
+ parts = [
214
+ preamble,
215
+ precompiled_template(locals),
216
+ precompiled_postamble(locals)
217
+ ]
218
+ [parts.join("\n"), preamble.count("\n") + 1]
219
+ end
220
+
221
+ # A string containing the (Ruby) source code for the template. The
222
+ # default Template#evaluate implementation requires either this method
223
+ # or the #precompiled method be overridden. When defined, the base
224
+ # Template guarantees correct file/line handling, locals support, custom
225
+ # scopes, and support for template compilation when the scope object
226
+ # allows it.
227
+ def precompiled_template(locals)
151
228
  raise NotImplementedError
152
229
  end
153
230
 
231
+ # Generates preamble code for initializing template state, and performing
232
+ # locals assignment. The default implementation performs locals
233
+ # assignment only. Lines included in the preamble are subtracted from the
234
+ # source line offset, so adding code to the preamble does not effect line
235
+ # reporting in Kernel::caller and backtraces.
236
+ def precompiled_preamble(locals)
237
+ locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
238
+ end
239
+
240
+ # Generates postamble code for the precompiled template source. The
241
+ # string returned from this method is appended to the precompiled
242
+ # template source.
243
+ def precompiled_postamble(locals)
244
+ ''
245
+ end
246
+
247
+ # The unique compiled method name for the locals keys provided.
248
+ def compiled_method_name(locals_keys)
249
+ @compiled_method_names[locals_keys] ||=
250
+ generate_compiled_method_name(locals_keys)
251
+ end
252
+
154
253
  private
155
- def local_assignment_code(locals)
156
- return ['', 1] if locals.empty?
157
- source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
158
- [source.join("\n"), source.length]
254
+ # Evaluate the template source in the context of the scope object.
255
+ def evaluate_source(scope, locals, &block)
256
+ source, offset = precompiled(locals)
257
+ scope.instance_eval(source, eval_file, line - offset)
159
258
  end
160
259
 
161
- def require_template_library(name)
162
- if Thread.list.size > 1
163
- warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
164
- "explicit require '#{name}' suggested."
260
+ # JRuby doesn't allow Object#instance_eval to yield to the block it's
261
+ # closed over. This is by design and (ostensibly) something that will
262
+ # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
263
+ # exhibits the behavior. More info here:
264
+ #
265
+ # http://jira.codehaus.org/browse/JRUBY-2599
266
+ #
267
+ # Additionally, JRuby's eval line reporting is off by one compared to
268
+ # all MRI versions tested.
269
+ #
270
+ # We redefine evaluate_source to work around both issues.
271
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
272
+ undef evaluate_source
273
+ def evaluate_source(scope, locals, &block)
274
+ source, offset = precompiled(locals)
275
+ file, lineno = eval_file, (line - offset) - 1
276
+ scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
277
+ end
278
+ end
279
+
280
+ def generate_compiled_method_name(locals_keys)
281
+ parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
282
+ digest = Digest::MD5.hexdigest(parts.join(':'))
283
+ "__tilt_#{digest}"
284
+ end
285
+
286
+ def compile_template_method(method_name, locals)
287
+ source, offset = precompiled(locals)
288
+ offset += 1
289
+ CompileSite.module_eval <<-RUBY, eval_file, line - offset
290
+ def #{method_name}(locals)
291
+ #{source}
292
+ end
293
+ RUBY
294
+
295
+ ObjectSpace.define_finalizer self,
296
+ Template.compiled_template_method_remover(CompileSite, method_name)
297
+ end
298
+
299
+ def self.compiled_template_method_remover(site, method_name)
300
+ proc { |oid| garbage_collect_compiled_template_method(site, method_name) }
301
+ end
302
+
303
+ def self.garbage_collect_compiled_template_method(site, method_name)
304
+ site.module_eval do
305
+ begin
306
+ remove_method(method_name)
307
+ rescue NameError
308
+ # method was already removed (ruby >= 1.9)
309
+ end
165
310
  end
166
- require name
167
311
  end
168
312
  end
169
313
 
@@ -173,7 +317,7 @@ module Tilt
173
317
  # cache = Tilt::Cache.new
174
318
  # cache.fetch(path, line, options) { Tilt.new(path, line, options) }
175
319
  #
176
- # Subsequent invocations return the already compiled template object.
320
+ # Subsequent invocations return the already loaded template object.
177
321
  class Cache
178
322
  def initialize
179
323
  @cache = {}
@@ -195,11 +339,11 @@ module Tilt
195
339
  # The template source is evaluated as a Ruby string. The #{} interpolation
196
340
  # syntax can be used to generated dynamic output.
197
341
  class StringTemplate < Template
198
- def compile!
342
+ def prepare
199
343
  @code = "%Q{#{data}}"
200
344
  end
201
345
 
202
- def template_source
346
+ def precompiled_template(locals)
203
347
  @code
204
348
  end
205
349
  end
@@ -210,44 +354,46 @@ module Tilt
210
354
  # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
211
355
  class ERBTemplate < Template
212
356
  def initialize_engine
213
- require_template_library 'erb' unless defined? ::ERB
357
+ return if defined? ::ERB
358
+ require_template_library 'erb'
214
359
  end
215
360
 
216
- def compile!
217
- @engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
361
+ def prepare
362
+ @outvar = (options[:outvar] || '_erbout').to_s
363
+ @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
218
364
  end
219
365
 
220
- def template_source
366
+ def precompiled_template(locals)
221
367
  @engine.src
222
368
  end
223
369
 
224
- def evaluate(scope, locals, &block)
225
- source, offset = local_assignment_code(locals)
226
- source = [source, template_source].join("\n")
227
-
228
- original_out_buf =
229
- scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
230
- scope.instance_variable_get(:@_out_buf)
231
-
232
- scope.instance_eval source, eval_file, line - offset
233
-
234
- output = scope.instance_variable_get(:@_out_buf)
235
- scope.instance_variable_set(:@_out_buf, original_out_buf)
236
-
237
- output
370
+ def precompiled_preamble(locals)
371
+ <<-RUBY
372
+ begin
373
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
374
+ #{super}
375
+ RUBY
238
376
  end
239
377
 
240
- private
378
+ def precompiled_postamble(locals)
379
+ <<-RUBY
380
+ #{super}
381
+ ensure
382
+ #{@outvar} = __original_outvar
383
+ end
384
+ RUBY
385
+ end
241
386
 
242
387
  # ERB generates a line to specify the character coding of the generated
243
388
  # source in 1.9. Account for this in the line offset.
244
389
  if RUBY_VERSION >= '1.9.0'
245
- def local_assignment_code(locals)
390
+ def precompiled(locals)
246
391
  source, offset = super
247
392
  [source, offset + 1]
248
393
  end
249
394
  end
250
395
  end
396
+
251
397
  %w[erb rhtml].each { |ext| register ext, ERBTemplate }
252
398
 
253
399
 
@@ -255,20 +401,28 @@ module Tilt
255
401
  # http://www.kuwata-lab.com/erubis/
256
402
  class ErubisTemplate < ERBTemplate
257
403
  def initialize_engine
258
- require_template_library 'erubis' unless defined? ::Erubis
404
+ return if defined? ::Erubis
405
+ require_template_library 'erubis'
259
406
  end
260
407
 
261
- def compile!
262
- Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
408
+ def prepare
409
+ @options.merge!(:preamble => false, :postamble => false)
410
+ @outvar = (options.delete(:outvar) || '_erbout').to_s
263
411
  @engine = ::Erubis::Eruby.new(data, options)
264
412
  end
265
413
 
266
- private
414
+ def precompiled_preamble(locals)
415
+ [super, "#{@outvar} = _buf = ''"].join("\n")
416
+ end
417
+
418
+ def precompiled_postamble(locals)
419
+ ["_buf", super].join("\n")
420
+ end
267
421
 
268
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
269
- # and adjust back.
422
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
423
+ # Override and adjust back.
270
424
  if RUBY_VERSION >= '1.9.0'
271
- def local_assignment_code(locals)
425
+ def precompiled(locals)
272
426
  source, offset = super
273
427
  [source, offset - 1]
274
428
  end
@@ -281,20 +435,54 @@ module Tilt
281
435
  # http://haml.hamptoncatlin.com/
282
436
  class HamlTemplate < Template
283
437
  def initialize_engine
284
- require_template_library 'haml' unless defined? ::Haml::Engine
438
+ return if defined? ::Haml::Engine
439
+ require_template_library 'haml'
285
440
  end
286
441
 
287
- def compile!
288
- @engine = ::Haml::Engine.new(data, haml_options)
442
+ def prepare
443
+ options = @options.merge(:filename => eval_file, :line => line)
444
+ @engine = ::Haml::Engine.new(data, options)
289
445
  end
290
446
 
291
447
  def evaluate(scope, locals, &block)
292
- @engine.render(scope, locals, &block)
448
+ if @engine.respond_to?(:precompiled_method_return_value, true)
449
+ super
450
+ else
451
+ @engine.render(scope, locals, &block)
452
+ end
293
453
  end
294
454
 
295
- private
296
- def haml_options
297
- options.merge(:filename => eval_file, :line => line)
455
+ # Precompiled Haml source. Taken from the precompiled_with_ambles
456
+ # method in Haml::Precompiler:
457
+ # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
458
+ def precompiled_template(locals)
459
+ @engine.precompiled
460
+ end
461
+
462
+ def precompiled_preamble(locals)
463
+ local_assigns = super
464
+ @engine.instance_eval do
465
+ <<-RUBY
466
+ begin
467
+ extend Haml::Helpers
468
+ _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
469
+ _erbout = _hamlout.buffer
470
+ __in_erb_template = true
471
+ _haml_locals = locals
472
+ #{local_assigns}
473
+ RUBY
474
+ end
475
+ end
476
+
477
+ def precompiled_postamble(locals)
478
+ @engine.instance_eval do
479
+ <<-RUBY
480
+ #{precompiled_method_return_value}
481
+ ensure
482
+ @haml_buffer = @haml_buffer.upper
483
+ end
484
+ RUBY
485
+ end
298
486
  end
299
487
  end
300
488
  register 'haml', HamlTemplate
@@ -306,15 +494,16 @@ module Tilt
306
494
  # Sass templates do not support object scopes, locals, or yield.
307
495
  class SassTemplate < Template
308
496
  def initialize_engine
309
- require_template_library 'sass' unless defined? ::Sass::Engine
497
+ return if defined? ::Sass::Engine
498
+ require_template_library 'sass'
310
499
  end
311
500
 
312
- def compile!
501
+ def prepare
313
502
  @engine = ::Sass::Engine.new(data, sass_options)
314
503
  end
315
504
 
316
505
  def evaluate(scope, locals, &block)
317
- @engine.render
506
+ @output ||= @engine.render
318
507
  end
319
508
 
320
509
  private
@@ -325,14 +514,36 @@ module Tilt
325
514
  register 'sass', SassTemplate
326
515
 
327
516
 
517
+ # Lessscss template implementation. See:
518
+ # http://lesscss.org/
519
+ #
520
+ # Less templates do not support object scopes, locals, or yield.
521
+ class LessTemplate < Template
522
+ def initialize_engine
523
+ return if defined? ::Less::Engine
524
+ require_template_library 'less'
525
+ end
526
+
527
+ def prepare
528
+ @engine = ::Less::Engine.new(data)
529
+ end
530
+
531
+ def evaluate(scope, locals, &block)
532
+ @engine.to_css
533
+ end
534
+ end
535
+ register 'less', LessTemplate
536
+
537
+
328
538
  # Builder template implementation. See:
329
539
  # http://builder.rubyforge.org/
330
540
  class BuilderTemplate < Template
331
541
  def initialize_engine
332
- require_template_library 'builder' unless defined?(::Builder)
542
+ return if defined?(::Builder)
543
+ require_template_library 'builder'
333
544
  end
334
545
 
335
- def compile!
546
+ def prepare
336
547
  end
337
548
 
338
549
  def evaluate(scope, locals, &block)
@@ -346,7 +557,7 @@ module Tilt
346
557
  xml.target!
347
558
  end
348
559
 
349
- def template_source
560
+ def precompiled_template(locals)
350
561
  data.to_str
351
562
  end
352
563
  end
@@ -368,10 +579,11 @@ module Tilt
368
579
  # time when using this template engine.
369
580
  class LiquidTemplate < Template
370
581
  def initialize_engine
371
- require_template_library 'liquid' unless defined? ::Liquid::Template
582
+ return if defined? ::Liquid::Template
583
+ require_template_library 'liquid'
372
584
  end
373
585
 
374
- def compile!
586
+ def prepare
375
587
  @engine = ::Liquid::Template.parse(data)
376
588
  end
377
589
 
@@ -381,9 +593,8 @@ module Tilt
381
593
  scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h }
382
594
  locals = scope.merge(locals)
383
595
  end
384
- # TODO: Is it possible to lazy yield ?
385
596
  locals['yield'] = block.nil? ? '' : yield
386
- locals['content'] = block.nil? ? '' : yield
597
+ locals['content'] = locals['yield']
387
598
  @engine.render(locals)
388
599
  end
389
600
  end
@@ -402,15 +613,17 @@ module Tilt
402
613
  end
403
614
 
404
615
  def initialize_engine
405
- require_template_library 'rdiscount' unless defined? ::RDiscount
616
+ return if defined? ::RDiscount
617
+ require_template_library 'rdiscount'
406
618
  end
407
619
 
408
- def compile!
620
+ def prepare
409
621
  @engine = RDiscount.new(data, *flags)
622
+ @output = nil
410
623
  end
411
624
 
412
625
  def evaluate(scope, locals, &block)
413
- @engine.to_html
626
+ @output ||= @engine.to_html
414
627
  end
415
628
  end
416
629
  register 'markdown', RDiscountTemplate
@@ -418,22 +631,24 @@ module Tilt
418
631
  register 'md', RDiscountTemplate
419
632
 
420
633
 
421
- # RedCloth implementation. See:
422
- # http://redcloth.org/
423
- class RedClothTemplate < Template
424
- def initialize_engine
425
- require_template_library 'redcloth' unless defined? ::RedCloth
426
- end
634
+ # RedCloth implementation. See:
635
+ # http://redcloth.org/
636
+ class RedClothTemplate < Template
637
+ def initialize_engine
638
+ return if defined? ::RedCloth
639
+ require_template_library 'redcloth'
640
+ end
427
641
 
428
- def compile!
429
- @engine = RedCloth.new(data)
430
- end
642
+ def prepare
643
+ @engine = RedCloth.new(data)
644
+ @output = nil
645
+ end
431
646
 
432
- def evaluate(scope, locals, &block)
433
- @engine.to_html
647
+ def evaluate(scope, locals, &block)
648
+ @output ||= @engine.to_html
649
+ end
434
650
  end
435
- end
436
- register 'textile', RedClothTemplate
651
+ register 'textile', RedClothTemplate
437
652
 
438
653
 
439
654
  # Mustache is written and maintained by Chris Wanstrath. See:
@@ -446,14 +661,16 @@ register 'textile', RedClothTemplate
446
661
  attr_reader :engine
447
662
 
448
663
  def initialize_engine
449
- require_template_library 'mustache' unless defined? ::Mustache
664
+ return if defined? ::Mustache
665
+ require_template_library 'mustache'
450
666
  end
451
667
 
452
- def compile!
668
+ def prepare
453
669
  Mustache.view_namespace = options[:namespace]
670
+ Mustache.view_path = options[:view_path] || options[:mustaches]
454
671
  @engine = options[:view] || Mustache.view_class(name)
455
672
  options.each do |key, value|
456
- next if %w[view namespace mustaches].include?(key.to_s)
673
+ next if %w[view view_path namespace mustaches].include?(key.to_s)
457
674
  @engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
458
675
  end
459
676
  end
@@ -482,6 +699,7 @@ register 'textile', RedClothTemplate
482
699
  end
483
700
  register 'mustache', MustacheTemplate
484
701
 
702
+
485
703
  # RDoc template. See:
486
704
  # http://rdoc.rubyforge.org/
487
705
  #
@@ -490,20 +708,39 @@ register 'textile', RedClothTemplate
490
708
  # engine.
491
709
  class RDocTemplate < Template
492
710
  def initialize_engine
493
- unless defined?(::RDoc::Markup)
494
- require_template_library 'rdoc/markup'
495
- require_template_library 'rdoc/markup/to_html'
496
- end
711
+ return if defined?(::RDoc::Markup)
712
+ require_template_library 'rdoc/markup'
713
+ require_template_library 'rdoc/markup/to_html'
497
714
  end
498
715
 
499
- def compile!
716
+ def prepare
500
717
  markup = RDoc::Markup::ToHtml.new
501
718
  @engine = markup.convert(data)
719
+ @output = nil
502
720
  end
503
721
 
504
722
  def evaluate(scope, locals, &block)
505
- @engine.to_s
723
+ @output ||= @engine.to_s
506
724
  end
507
725
  end
508
726
  register 'rdoc', RDocTemplate
727
+
728
+
729
+ # CoffeeScript info:
730
+ # http://jashkenas.github.com/coffee-script/
731
+ class CoffeeTemplate < Template
732
+ def initialize_engine
733
+ return if defined? ::CoffeeScript
734
+ require_template_library 'coffee-script'
735
+ end
736
+
737
+ def prepare
738
+ @output = nil
739
+ end
740
+
741
+ def evaluate(scope, locals, &block)
742
+ @output ||= ::CoffeeScript::compile(data, options)
743
+ end
744
+ end
745
+ register 'coffee', CoffeeTemplate
509
746
  end