roda 2.2.0 → 2.3.0

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