roda 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +34 -0
  3. data/Rakefile +22 -46
  4. data/doc/release_notes/2.3.0.txt +109 -0
  5. data/lib/roda/plugins/assets.rb +2 -1
  6. data/lib/roda/plugins/caching.rb +1 -1
  7. data/lib/roda/plugins/chunked.rb +1 -1
  8. data/lib/roda/plugins/error_email.rb +1 -1
  9. data/lib/roda/plugins/head.rb +6 -0
  10. data/lib/roda/plugins/heartbeat.rb +40 -0
  11. data/lib/roda/plugins/json.rb +23 -3
  12. data/lib/roda/plugins/json_parser.rb +72 -0
  13. data/lib/roda/plugins/mailer.rb +22 -5
  14. data/lib/roda/plugins/named_templates.rb +2 -2
  15. data/lib/roda/plugins/path_rewriter.rb +82 -0
  16. data/lib/roda/plugins/precompile_templates.rb +87 -0
  17. data/lib/roda/plugins/render.rb +111 -43
  18. data/lib/roda/plugins/render_each.rb +1 -1
  19. data/lib/roda/plugins/shared_vars.rb +1 -1
  20. data/lib/roda/plugins/view_options.rb +28 -3
  21. data/lib/roda/version.rb +1 -1
  22. data/spec/composition_spec.rb +3 -3
  23. data/spec/env_spec.rb +1 -1
  24. data/spec/freeze_spec.rb +6 -6
  25. data/spec/integration_spec.rb +16 -15
  26. data/spec/matchers_spec.rb +110 -110
  27. data/spec/opts_spec.rb +8 -8
  28. data/spec/plugin/_erubis_escaping_spec.rb +34 -3
  29. data/spec/plugin/all_verbs_spec.rb +8 -8
  30. data/spec/plugin/assets_spec.rb +164 -150
  31. data/spec/plugin/backtracking_array_spec.rb +18 -18
  32. data/spec/plugin/caching_spec.rb +70 -70
  33. data/spec/plugin/chunked_spec.rb +38 -38
  34. data/spec/plugin/class_level_routing_spec.rb +78 -78
  35. data/spec/plugin/content_for_spec.rb +2 -2
  36. data/spec/plugin/cookies_spec.rb +4 -4
  37. data/spec/plugin/csrf_spec.rb +8 -8
  38. data/spec/plugin/default_headers_spec.rb +6 -6
  39. data/spec/plugin/delay_build_spec.rb +7 -6
  40. data/spec/plugin/delegate_spec.rb +2 -2
  41. data/spec/plugin/delete_empty_headers_spec.rb +2 -2
  42. data/spec/plugin/drop_body_spec.rb +6 -6
  43. data/spec/plugin/empty_root_spec.rb +3 -3
  44. data/spec/plugin/environments_spec.rb +7 -7
  45. data/spec/plugin/error_email_spec.rb +23 -23
  46. data/spec/plugin/error_handler_spec.rb +14 -14
  47. data/spec/plugin/flash_spec.rb +30 -29
  48. data/spec/plugin/h_spec.rb +1 -1
  49. data/spec/plugin/halt_spec.rb +16 -16
  50. data/spec/plugin/hash_matcher_spec.rb +5 -5
  51. data/spec/plugin/head_spec.rb +10 -10
  52. data/spec/plugin/header_matchers_spec.rb +13 -13
  53. data/spec/plugin/heartbeat_spec.rb +74 -0
  54. data/spec/plugin/hooks_spec.rb +20 -20
  55. data/spec/plugin/indifferent_params_spec.rb +1 -1
  56. data/spec/plugin/json_parser_spec.rb +72 -0
  57. data/spec/plugin/json_spec.rb +22 -9
  58. data/spec/plugin/mailer_spec.rb +72 -58
  59. data/spec/plugin/match_affix_spec.rb +2 -2
  60. data/spec/plugin/middleware_spec.rb +7 -7
  61. data/spec/plugin/module_include_spec.rb +4 -4
  62. data/spec/plugin/multi_route_spec.rb +66 -66
  63. data/spec/plugin/multi_run_spec.rb +21 -21
  64. data/spec/plugin/named_templates_spec.rb +6 -6
  65. data/spec/plugin/not_allowed_spec.rb +17 -17
  66. data/spec/plugin/not_found_spec.rb +14 -14
  67. data/spec/plugin/padrino_render_spec.rb +2 -2
  68. data/spec/plugin/param_matchers_spec.rb +6 -6
  69. data/spec/plugin/partials_spec.rb +3 -3
  70. data/spec/plugin/pass_spec.rb +7 -7
  71. data/spec/plugin/path_matchers_spec.rb +6 -6
  72. data/spec/plugin/path_rewriter_spec.rb +37 -0
  73. data/spec/plugin/path_spec.rb +41 -40
  74. data/spec/plugin/per_thread_caching_spec.rb +6 -6
  75. data/spec/plugin/precompile_templates_spec.rb +74 -0
  76. data/spec/plugin/render_each_spec.rb +4 -4
  77. data/spec/plugin/render_spec.rb +179 -76
  78. data/spec/plugin/shared_vars_spec.rb +4 -4
  79. data/spec/plugin/sinatra_helpers_spec.rb +121 -121
  80. data/spec/plugin/slash_path_empty_spec.rb +10 -10
  81. data/spec/plugin/static_spec.rb +4 -4
  82. data/spec/plugin/streaming_spec.rb +11 -11
  83. data/spec/plugin/symbol_matchers_spec.rb +24 -24
  84. data/spec/plugin/symbol_views_spec.rb +3 -3
  85. data/spec/plugin/view_options_spec.rb +10 -10
  86. data/spec/plugin_spec.rb +2 -2
  87. data/spec/redirect_spec.rb +10 -10
  88. data/spec/request_spec.rb +8 -8
  89. data/spec/response_spec.rb +23 -23
  90. data/spec/session_spec.rb +4 -4
  91. data/spec/spec_helper.rb +5 -19
  92. data/spec/version_spec.rb +4 -4
  93. data/spec/views/iv.erb +1 -0
  94. metadata +16 -5
@@ -87,6 +87,14 @@ class Roda
87
87
  # ""
88
88
  # end
89
89
  #
90
+ # If while preparing the email you figure out you don't want to send an
91
+ # email, call +no_mail!+:
92
+ #
93
+ # r.mail '/welcome/:d' do |id|
94
+ # no_mail! unless user = User[id]
95
+ # # ...
96
+ # end
97
+ #
90
98
  # By default, the mailer uses text/plain as the Content-Type for emails.
91
99
  # You can override the default by specifying a :content_type option when
92
100
  # loading the plugin:
@@ -128,15 +136,19 @@ class Roda
128
136
  # calling +deliver+ to send the mail.
129
137
  def mail(path, *args)
130
138
  mail = ::Mail.new
131
- unless mail.equal?(new(PATH_INFO=>path, SCRIPT_NAME=>EMPTY_STRING, REQUEST_METHOD=>MAIL, RACK_INPUT=>StringIO.new, RODA_MAIL=>mail, RODA_MAIL_ARGS=>args).call(&route_block))
132
- raise Error, "route did not return mail instance for #{path.inspect}, #{args.inspect}"
139
+ catch(:no_mail) do
140
+ unless mail.equal?(new(PATH_INFO=>path, SCRIPT_NAME=>EMPTY_STRING, REQUEST_METHOD=>MAIL, RACK_INPUT=>StringIO.new, RODA_MAIL=>mail, RODA_MAIL_ARGS=>args).call(&route_block))
141
+ raise Error, "route did not return mail instance for #{path.inspect}, #{args.inspect}"
142
+ end
143
+ mail
133
144
  end
134
- mail
135
145
  end
136
146
 
137
147
  # Calls +mail+ and immediately sends the resulting mail.
138
148
  def sendmail(*args)
139
- mail(*args).deliver
149
+ if m = mail(*args)
150
+ m.deliver
151
+ end
140
152
  end
141
153
  end
142
154
 
@@ -149,7 +161,7 @@ class Roda
149
161
  def mail(*args)
150
162
  if @env[REQUEST_METHOD] == MAIL
151
163
  if_match(args) do |*vs|
152
- yield *(vs + @env[RODA_MAIL_ARGS])
164
+ yield(*(vs + @env[RODA_MAIL_ARGS]))
153
165
  end
154
166
  end
155
167
  end
@@ -232,6 +244,11 @@ class Roda
232
244
  nil
233
245
  end
234
246
 
247
+ # Signal that no mail should be sent for this request.
248
+ def no_mail!
249
+ throw :no_mail
250
+ end
251
+
235
252
  private
236
253
 
237
254
  # Set the text_part or html_part (depending on the method) in the related email,
@@ -79,9 +79,9 @@ class Roda
79
79
  def find_template(options)
80
80
  if options[:template] && (template_opts, block = opts[:named_templates][template_name(options)]; block)
81
81
  if template_opts
82
- options = template_opts.merge(options)
82
+ options = Hash[template_opts].merge!(options)
83
83
  else
84
- options = options.dup
84
+ options = Hash[options]
85
85
  end
86
86
 
87
87
  options[:inline] = instance_exec(&block)
@@ -0,0 +1,82 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The path_rewriter plugin allows you to rewrite the remaining path
4
+ # or the path info for requests. This is useful if you want to
5
+ # transparently treat some paths the same as other paths.
6
+ #
7
+ # By default, +rewrite_path+ will rewrite just the remaining path. So
8
+ # only routing in the current Roda app will be affected. This is useful
9
+ # if you have other code in your app that uses PATH_INFO and needs to
10
+ # see the original PATH_INFO (for example, when using relative links).
11
+ #
12
+ # rewrite_path '/a', '/b'
13
+ # # PATH_INFO '/a' => remaining_path '/b'
14
+ # # PATH_INFO '/a/c' => remaining_path '/b/c'
15
+ #
16
+ # In some cases, you may want to override PATH_INFO for the rewritten
17
+ # paths, such as when you are passing the request to another Rack app.
18
+ # For those cases, you can use the <tt>:path_info => true</tt> option to
19
+ # +rewrite_path+.
20
+ #
21
+ # rewrite_path '/a', '/b', :path_info => true
22
+ # # PATH_INFO '/a' => PATH_INFO '/b'
23
+ # # PATH_INFO '/a/c' => PATH_INFO '/b/c'
24
+ #
25
+ # If you pass a string to +rewrite_path+, it will rewrite all paths starting
26
+ # with that string. You can provide a regexp if you want more complete control,
27
+ # such as only matching exact paths.
28
+ #
29
+ # rewrite_path /\A\/a\z/, '/b'
30
+ # # PATH_INFO '/a' => remaining_path '/b'
31
+ # # PATH_INFO '/a/c' => remaining_path '/a/c', no change
32
+ #
33
+ # All path rewrites are applied in order, so if a path is rewritten by one rewrite,
34
+ # it can be rewritten again by a later rewrite. Note that PATH_INFO rewrites are
35
+ # processed before remaining_path rewrites.
36
+ module PathRewriter
37
+ PATH_INFO = 'PATH_INFO'.freeze
38
+ OPTS={}.freeze
39
+
40
+ def self.configure(app)
41
+ app.instance_exec do
42
+ app.opts[:remaining_path_rewrites] ||= []
43
+ app.opts[:path_info_rewrites] ||= []
44
+ end
45
+ end
46
+
47
+ module ClassMethods
48
+ # Freeze the path rewrite metadata.
49
+ def freeze
50
+ opts[:remaining_path_rewrites].freeze
51
+ opts[:path_info_rewrites].freeze
52
+ super
53
+ end
54
+
55
+ # Record a path rewrite from path +was+ to path +is+. Options:
56
+ # :path_info :: Modify PATH_INFO, not just remaining path.
57
+ def rewrite_path(was, is, opts=OPTS)
58
+ was = /\A#{Regexp.escape(was)}/ unless was.is_a?(Regexp)
59
+ array = @opts[opts[:path_info] ? :path_info_rewrites : :remaining_path_rewrites]
60
+ array << [was, is.dup.freeze].freeze
61
+ end
62
+ end
63
+
64
+ module RequestMethods
65
+ # Rewrite remaining_path and/or PATH_INFO based on the path rewrites.
66
+ def initialize(scope, env)
67
+ path_info = env[PATH_INFO]
68
+ scope.class.opts[:path_info_rewrites].each do |was, is|
69
+ path_info.sub!(was, is)
70
+ end
71
+ super
72
+ remaining_path = @remaining_path = @remaining_path.dup
73
+ scope.class.opts[:remaining_path_rewrites].each do |was, is|
74
+ remaining_path.sub!(was, is)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ register_plugin(:path_rewriter, PathRewriter)
81
+ end
82
+ end
@@ -0,0 +1,87 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ # The precompile_templates plugin adds support for precompiling template code.
4
+ # This can result in a large memory savings for applications that have large
5
+ # templates or a large number of small templates if the application uses a
6
+ # forking webserver. By default, template compilation is lazy, so all the
7
+ # child processes in a forking webserver will have their own copy of the
8
+ # compiled template. By using the precompile_templates plugin, you can
9
+ # precompile the templates in the parent process before forking, and then
10
+ # all of the child processes can use the same precompiled templates, which
11
+ # saves memory.
12
+ #
13
+ # After loading the plugin, you can call +precompile_templates+ with
14
+ # the pattern of templates you would like to precompile:
15
+ #
16
+ # plugin :precompile_templates
17
+ # precompile_templates "views/\*\*/*.erb"
18
+ #
19
+ # That will precompile all erb template files in the views directory or
20
+ # any subdirectory.
21
+ #
22
+ # If the templates use local variables, you need to specify which local
23
+ # variables to precompile, which should be an array of symbols:
24
+ #
25
+ # precompile_templates 'views/users/_*.erb', :locals=>[:user]
26
+ #
27
+ # Note that if you have multiple local variables and are not using a Tilt
28
+ # version greater than 2.0.1, you should specify the :locals option in the
29
+ # same order as the keys in the :locals hash you pass to render/view. Since
30
+ # hashes are not ordered in ruby 1.8, you should not attempt to precompile
31
+ # templates that use :locals on ruby 1.8 unless you are using a Tilt version
32
+ # greater than 2.0.1. If you are running the Tilt master branch, you can
33
+ # force sorting of locals using the +:sort_locals+ option when loading the
34
+ # plugin.
35
+ #
36
+ # You can specify other render options when calling +precompile_templates+,
37
+ # including +:cache_key+, +:template_class+, and +:template_opts+. If you
38
+ # are passing any of those options to render/view for the template, you
39
+ # should pass the same options when precompiling the template.
40
+ #
41
+ # To compile inline templates, just pass a single hash containing an :inline
42
+ # to +precompile_templates+:
43
+ #
44
+ # precompile_templates :inline=>some_template_string
45
+ module PrecompileTemplates
46
+ OPTS = {}.freeze
47
+
48
+ # Load the render plugin as precompile_templates depends on it.
49
+ # Default to sorting the locals if the Tilt version is greater than 2.0.1.
50
+ def self.load_dependencies(app, opts=OPTS)
51
+ app.plugin :render
52
+ app.opts[:precompile_templates_sort] = opts.fetch(:sort_locals, Tilt::VERSION > '2.0.1')
53
+ end
54
+
55
+ module ClassMethods
56
+ # Precompile the templates using the given options. See PrecompileTemplates
57
+ # for details.
58
+ def precompile_templates(pattern, opts=OPTS)
59
+ if pattern.is_a?(Hash)
60
+ opts = pattern.merge(opts)
61
+ end
62
+
63
+ locals = opts[:locals] || []
64
+ if locals && self.opts[:precompile_templates_sort]
65
+ locals = locals.sort{|x,y| x.to_s <=> y.to_s}
66
+ end
67
+
68
+ compile_opts = if pattern.is_a?(Hash)
69
+ [opts]
70
+ else
71
+ Dir[pattern].map{|file| opts.merge(:path=>File.expand_path(file))}
72
+ end
73
+
74
+ instance = allocate
75
+ compile_opts.each do |compile_opt|
76
+ template = instance.send(:retrieve_template, compile_opt)
77
+ template.send(:compiled_method, locals)
78
+ end
79
+
80
+ nil
81
+ end
82
+ end
83
+ end
84
+
85
+ register_plugin(:precompile_templates, PrecompileTemplates)
86
+ end
87
+ end
@@ -22,12 +22,15 @@ class Roda
22
22
  #
23
23
  # plugin :render, :engine=>'haml', :views=>'admin_views'
24
24
  #
25
- # The following options are supported:
25
+ # = Plugin Options
26
+ #
27
+ # The following plugin options are supported:
26
28
  #
27
29
  # :cache :: nil/false to not cache templates (useful for development), defaults
28
30
  # to true unless RACK_ENV is development to automatically use the
29
31
  # default template cache.
30
- # :engine :: The tilt engine to use for rendering, defaults to 'erb'.
32
+ # :engine :: The tilt engine to use for rendering, also the default file extension for
33
+ # templates, defaults to 'erb'.
31
34
  # :escape :: Use Roda's Erubis escaping support, which makes <tt><%= %></tt> escape output,
32
35
  # <tt><%== %></tt> not escape output, and handles postfix conditions inside
33
36
  # <tt><%= %></tt> tags.
@@ -36,25 +39,32 @@ class Roda
36
39
  # :escaper :: Object used for escaping output of <tt><%= %></tt>, when :escape is used,
37
40
  # overriding the default. If given, object should respond to +escape_xml+ with
38
41
  # a single argument and return an output string.
39
- # :ext :: The file extension to assume for view files, defaults to the :engine
40
- # option.
41
42
  # :layout :: The base name of the layout file, defaults to 'layout'.
42
43
  # :layout_opts :: The options to use when rendering the layout, if different
43
44
  # from the default options.
44
- # :template_opts :: The tilt options used when rendering templates, defaults to
45
- # <tt>{:outvar=>'@_out_buf', :default_encoding=>Encoding.default_external}</tt>.
45
+ # :template_opts :: The tilt options used when rendering all templates. defaults to:
46
+ # <tt>{:outvar=>'@_out_buf', :default_encoding=>Encoding.default_external}</tt>.
47
+ # :engine_opts :: The tilt options to use per template engine. Keys are
48
+ # engine strings, values are hashes of template options.
46
49
  # :views :: The directory holding the view files, defaults to the 'views' subdirectory of the
47
50
  # application's :root option (the process's working directory by default).
48
51
  #
52
+ # = Render/View Method Options
53
+ #
49
54
  # Most of these options can be overridden at runtime by passing options
50
55
  # to the +view+ or +render+ methods:
51
56
  #
52
- # view('foo', :ext=>'html.erb')
57
+ # view('foo', :engine=>'html.erb')
53
58
  # render('foo', :views=>'admin_views')
54
59
  #
55
- # There are a couple of additional options to +view+ and +render+ that are
60
+ # There are additional options to +view+ and +render+ that are
56
61
  # available at runtime:
57
62
  #
63
+ # :cache :: Set to false to not cache this template, even when
64
+ # caching is on by default. Set to true to force caching for
65
+ # this template, even when the default is to not cache (e.g.
66
+ # when using the :template_block option).
67
+ # :cache_key :: Explicitly set the hash key to use when caching.
58
68
  # :content :: Only respected by +view+, provides the content to render
59
69
  # inside the layout, instead of rendering a template to get
60
70
  # the content.
@@ -62,13 +72,14 @@ class Roda
62
72
  # for template code in a file.
63
73
  # :locals :: Hash of local variables to make available inside the template.
64
74
  # :path :: Use the value given as the full pathname for the file, instead
65
- # of using the :views and :ext option in combination with the
75
+ # of using the :views and :engine option in combination with the
66
76
  # template name.
67
77
  # :template :: Provides the name of the template to use. This allows you
68
78
  # pass a single options hash to the render/view method, while
69
79
  # still allowing you to specify the template name.
70
80
  # :template_block :: Pass this block when creating the underlying template,
71
- # ignored when using :inline.
81
+ # ignored when using :inline. Disables caching of the
82
+ # template by default.
72
83
  # :template_class :: Provides the template class to use, inside of using
73
84
  # Tilt or <tt>Tilt[:engine]</tt>.
74
85
  #
@@ -80,6 +91,27 @@ class Roda
80
91
  # If you pass a hash as the first argument to +view+ or +render+, it should
81
92
  # have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as
82
93
  # one of the keys.
94
+ #
95
+ # = Speeding Up Template Rendering
96
+ #
97
+ # By default, determining the cache key to use for the template can be a lot
98
+ # of work. If you specify the +:cache_key+ option, you can save Roda from
99
+ # having to do that work, which will make your application faster. However,
100
+ # if you do this, you need to make sure you choose a correct key.
101
+ #
102
+ # If your application uses a unique template per path, in that the same
103
+ # path never uses more than one template, you can use the +view_options+ plugin
104
+ # and do:
105
+ #
106
+ # set_view_options :cache_key=>r.path_info
107
+ #
108
+ # at the top of your route block. You can even do this if you do have paths
109
+ # that use more than one template, as long as you specify +:cache_key+
110
+ # specifically when rendering in those paths.
111
+ #
112
+ # If you use a single layout in your application, you can also make layout
113
+ # rendering faster by specifying +:cache_key+ inside the +:layout_opts+
114
+ # plugin option.
83
115
  module Render
84
116
  OPTS={}.freeze
85
117
 
@@ -98,12 +130,12 @@ class Roda
98
130
  app.opts[:render][:orig_opts] = opts
99
131
 
100
132
  opts = app.opts[:render]
101
- opts[:engine] ||= "erb"
102
- opts[:ext] = nil unless opts.has_key?(:ext)
103
- opts[:views] = File.expand_path(opts[:views]||"views", app.opts[:root])
133
+ opts[:engine] = (opts[:engine] || opts[:ext] || "erb").dup.freeze
134
+ opts[:views] = File.expand_path(opts[:views]||"views", app.opts[:root]).freeze
135
+ opts[:cache] = app.thread_safe_cache if opts.fetch(:cache, ENV['RACK_ENV'] != 'development')
136
+
104
137
  opts[:layout_opts] = (opts[:layout_opts] || {}).dup
105
138
  opts[:layout_opts][:_is_layout] = true
106
-
107
139
  if layout = opts.fetch(:layout, true)
108
140
  opts[:layout] = true unless opts.has_key?(:layout)
109
141
 
@@ -116,6 +148,7 @@ class Roda
116
148
  opts[:layout_opts][:template] = layout
117
149
  end
118
150
  end
151
+ opts[:layout_opts].freeze
119
152
 
120
153
  template_opts = opts[:template_opts] = (opts[:template_opts] || {}).dup
121
154
  template_opts[:outvar] ||= '@_out_buf'
@@ -131,9 +164,14 @@ class Roda
131
164
  ::Erubis::XmlHelper
132
165
  end
133
166
  end
134
- opts[:cache] = app.thread_safe_cache if opts.fetch(:cache, ENV['RACK_ENV'] != 'development')
135
- opts[:layout_opts].freeze
136
- opts[:template_opts].freeze
167
+ template_opts.freeze
168
+
169
+ engine_opts = opts[:engine_opts] = (opts[:engine_opts] || {}).dup
170
+ engine_opts.to_a.each do |k,v|
171
+ engine_opts[k] = v.dup.freeze
172
+ end
173
+ engine_opts.freeze
174
+
137
175
  opts.freeze
138
176
  end
139
177
 
@@ -157,13 +195,9 @@ class Roda
157
195
  module InstanceMethods
158
196
  # Render the given template. See Render for details.
159
197
  def render(template, opts = OPTS, &block)
160
- opts = find_template(parse_template_opts(template, opts))
161
- cached_template(opts) do
162
- template_opts = render_opts[:template_opts]
163
- current_template_opts = opts[:template_opts]
164
- template_opts = template_opts.merge(current_template_opts) if current_template_opts
165
- opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
166
- end.render(self, (opts[:locals]||OPTS), &block)
198
+ opts = parse_template_opts(template, opts)
199
+ merge_render_locals(opts)
200
+ retrieve_template(opts).render(self, (opts[:locals]||OPTS), &block)
167
201
  end
168
202
 
169
203
  # Return the render options for the instance's class. While this
@@ -196,8 +230,7 @@ class Roda
196
230
  # If caching templates, attempt to retrieve the template from the cache. Otherwise, just yield
197
231
  # to get the template.
198
232
  def cached_template(opts, &block)
199
- if cache = render_opts[:cache]
200
- key = opts[:key]
233
+ if (cache = render_opts[:cache]) && (key = opts[:cache_key])
201
234
  unless template = cache[key]
202
235
  template = cache[key] = yield
203
236
  end
@@ -210,49 +243,86 @@ class Roda
210
243
  # Given the template name and options, set the template class, template path/content,
211
244
  # template block, and locals to use for the render in the passed options.
212
245
  def find_template(opts)
246
+ render_opts = render_opts()
247
+ engine_override = opts[:engine] ||= opts[:ext]
248
+ engine = opts[:engine] ||= render_opts[:engine]
213
249
  if content = opts[:inline]
214
250
  path = opts[:path] = content
215
- template_class = opts[:template_class] ||= ::Tilt[opts[:engine] || render_opts[:engine]]
251
+ template_class = opts[:template_class] ||= ::Tilt[engine]
216
252
  opts[:template_block] = Proc.new{content}
217
253
  else
254
+ opts[:views] ||= render_opts[:views]
218
255
  path = opts[:path] ||= template_path(opts)
219
256
  template_class = opts[:template_class]
220
257
  opts[:template_class] ||= ::Tilt
221
258
  end
222
259
 
223
260
  if render_opts[:cache]
224
- template_opts = opts[:template_opts]
225
- template_block = opts[:template_block] if !content
261
+ if (cache = opts[:cache]).nil?
262
+ cache = content || !opts[:template_block]
263
+ end
264
+
265
+ if cache
266
+ template_block = opts[:template_block] unless content
267
+ template_opts = opts[:template_opts]
226
268
 
227
- key = if template_class || template_opts || template_block
228
- [path, template_class, template_opts, template_block]
269
+ opts[:cache_key] ||= if template_class || engine_override || template_opts || template_block
270
+ [path, template_class, engine_override, template_opts, template_block]
271
+ else
272
+ path
273
+ end
229
274
  else
230
- path
275
+ opts.delete(:cache_key)
231
276
  end
232
- opts[:key] = key
233
277
  end
234
278
 
279
+ opts
280
+ end
281
+
282
+ # Merge any :locals specified in the render_opts into the :locals option given.
283
+ def merge_render_locals(opts)
235
284
  if !opts[:_is_layout] && (r_locals = render_opts[:locals])
236
285
  opts[:locals] = if locals = opts[:locals]
237
- r_locals.merge(locals)
286
+ Hash[r_locals].merge!(locals)
238
287
  else
239
288
  r_locals
240
289
  end
241
290
  end
242
-
243
- opts
244
291
  end
245
292
 
246
293
  # Return a single hash combining the template and opts arguments.
247
294
  def parse_template_opts(template, opts)
248
- template = {:template=>template} unless template.is_a?(Hash)
249
- opts.merge(template)
295
+ opts = Hash[opts]
296
+ if template.is_a?(Hash)
297
+ opts.merge!(template)
298
+ else
299
+ opts[:template] = template
300
+ opts
301
+ end
250
302
  end
251
303
 
252
304
  # The default render options to use. These set defaults that can be overridden by
253
305
  # providing a :layout_opts option to the view/render method.
254
306
  def render_layout_opts
255
- render_opts[:layout_opts].dup
307
+ Hash[render_opts[:layout_opts]]
308
+ end
309
+
310
+ # Retrieve the Tilt::Template object for the given template and opts.
311
+ def retrieve_template(opts)
312
+ unless opts[:cache_key] && opts[:cache] != false
313
+ found_template_opts = opts = find_template(opts)
314
+ end
315
+ cached_template(opts) do
316
+ opts = found_template_opts || find_template(opts)
317
+ template_opts = render_opts[:template_opts]
318
+ if engine_opts = render_opts[:engine_opts][opts[:engine]]
319
+ template_opts = Hash[template_opts].merge!(engine_opts)
320
+ end
321
+ if current_template_opts = opts[:template_opts]
322
+ template_opts = Hash[template_opts].merge!(current_template_opts)
323
+ end
324
+ opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block])
325
+ end
256
326
  end
257
327
 
258
328
  # The name to use for the template. By default, just converts the :template option to a string.
@@ -262,8 +332,7 @@ class Roda
262
332
 
263
333
  # The template path for the given options.
264
334
  def template_path(opts)
265
- render_opts = render_opts()
266
- "#{opts[:views] || render_opts[:views]}/#{template_name(opts)}.#{opts[:ext] || render_opts[:ext] || render_opts[:engine]}"
335
+ "#{opts[:views]}/#{template_name(opts)}.#{opts[:engine]}"
267
336
  end
268
337
 
269
338
  # If a layout should be used, return a hash of options for
@@ -274,7 +343,7 @@ class Roda
274
343
  layout_opts = render_layout_opts
275
344
  if l_opts = opts[:layout_opts]
276
345
  if (l_locals = l_opts[:locals]) && (layout_locals = layout_opts[:locals])
277
- set_locals = layout_locals.merge(l_locals)
346
+ set_locals = Hash[layout_locals].merge!(l_locals)
278
347
  end
279
348
  layout_opts.merge!(l_opts)
280
349
  if set_locals
@@ -294,7 +363,6 @@ class Roda
294
363
  layout_opts
295
364
  end
296
365
  end
297
-
298
366
  end
299
367
  end
300
368