sinatra-cache 0.2.3 → 0.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.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
data/lib/sinatra/cache.rb CHANGED
@@ -1,139 +1,13 @@
1
- require 'fileutils'
1
+
2
2
  require 'sinatra/base'
3
3
 
4
- module Sinatra
5
-
6
- # Sinatra Caching module
7
- #
8
- # TODO:: Need to write documentation here
9
- #
4
+ module Sinatra
10
5
  module Cache
11
-
12
- VERSION = 'Sinatra::Cache v0.2.2'
13
- def self.version; VERSION; end
14
-
15
-
16
- module Helpers
17
-
18
- # Caches the given URI to a html file in /public
19
- #
20
- # <b>Usage:</b>
21
- # >> cache( erb(:contact, :layout => :layout))
22
- # => returns the HTML output written to /public/<CACHE_DIR_PATH>/contact.html
23
- #
24
- # Also accepts an Options Hash, with the following options:
25
- # * :extension => in case you need to change the file extension
26
- #
27
- # TODO:: implement the opts={} hash functionality. What other options are needed?
28
- #
29
- def cache(content, opts={})
30
- return content unless options.cache_enabled
31
-
32
- unless content.nil?
33
- content = "#{content}\n#{page_cached_timestamp}"
34
- path = cache_page_path(request.path_info,opts)
35
- FileUtils.makedirs(File.dirname(path))
36
- open(path, 'wb+') { |f| f << content }
37
- log("Cached Page: [#{path}]",:info)
38
- content
39
- end
40
- end
41
-
42
- # Expires the cached URI (as .html file) in /public
43
- #
44
- # <b>Usage:</b>
45
- # >> cache_expire('/contact')
46
- # => deletes the /public/<CACHE_DIR_PATH>contact.html page
47
- #
48
- # get '/contact' do
49
- # cache_expire # deletes the /public/<CACHE_DIR_PATH>contact.html page as well
50
- # end
51
- #
52
- # TODO:: implement the options={} hash functionality. What options are really needed ?
53
- def cache_expire(path = nil, opts={})
54
- return unless options.cache_enabled
55
-
56
- path = (path.nil?) ? cache_page_path(request.path_info) : cache_page_path(path)
57
- if File.exist?(path)
58
- File.delete(path)
59
- log("Expired Page deleted at: [#{path}]",:info)
60
- else
61
- log("No Expired Page was found at the path: [#{path}]",:info)
62
- end
63
- end
64
-
65
- # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
66
- #
67
- # *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
68
- # inside the <head> tag. Many hours wasted on that lesson ;-)
69
- #
70
- # <b>Usage:</b>
71
- # >> <%= page_cached_timestamp %>
72
- # => <!-- page cached: 2009-02-24 12:00:00 -->
73
- #
74
- def page_cached_timestamp
75
- "<!-- page cached: #{Time.now.strftime("%Y-%d-%m %H:%M:%S")} -->\n" if options.cache_enabled
76
- end
77
-
78
-
79
- private
80
-
81
- # Establishes the file name of the cached file from the path given
82
- #
83
- # TODO:: implement the opts={} functionality, and support for custom extensions on a per request basis.
84
- #
85
- def cache_file_name(path,opts={})
86
- name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
87
- name << options.cache_page_extension unless (name.split('/').last || name).include? '.'
88
- return name
89
- end
90
-
91
- # Sets the full path to the cached page/file
92
- # Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
93
- #
94
- def cache_page_path(path,opts={})
95
- # test if given a full path rather than relative path, otherwise join the public path to cache_dir
96
- # and ensure it is a full path
97
- cache_dir = (options.cache_output_dir == File.expand_path(options.cache_output_dir)) ?
98
- options.cache_output_dir : File.expand_path("#{options.public}/#{options.cache_output_dir}")
99
- cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
100
- "#{cache_dir}/#{cache_file_name(path,opts)}"
101
- end
102
-
103
- # TODO:: this implementation really stinks, how do I incorporate Sinatra's logger??
104
- def log(msg,scope=:debug)
105
- if options.cache_logging
106
- "Log: msg=[#{msg}]" if scope == options.cache_logging_level
107
- else
108
- # just ignore the stuff...
109
- # puts "just ignoring msg=[#{msg}] since cache_logging => [#{options.cache_logging.to_s}]"
110
- end
111
- end
112
-
113
- end #/module Helpers
114
-
115
-
116
- # Sets the default options:
117
- #
118
- # * +:cache_enabled+ => toggle for the cache functionality. Default is: +true+
119
- # * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
120
- # * +:cache_dir+ => sets cache directory where cached files are stored. Default is: ''(empty) == root of /public.<br>
121
- # set to empty, since the ideal 'system/cache/' does not work with Passenger & mod_rewrite :(
122
- # * +cache_logging+ => toggle for logging the cache calls. Default is: +true+
123
- # * +cache_logging_level+ => sets the level of the cache logger. Default is: <tt>:info</tt>.<br>
124
- # Options:(unused atm) [:info, :warn, :debug]
125
- #
126
- def self.registered(app)
127
- app.helpers(Cache::Helpers)
128
- app.set :cache_enabled, true
129
- app.set :cache_page_extension, '.html'
130
- app.set :cache_output_dir, ''
131
- app.set :cache_logging, true
132
- app.set :cache_logging_level, :info
133
- end
134
-
135
- end #/module Cache
136
-
137
- register(Sinatra::Cache)
138
-
139
- end #/module Sinatra
6
+ VERSION = '0.3.0' unless const_defined?(:VERSION)
7
+ def self.version; "Sinatra::Cache v#{VERSION}"; end
8
+ end #/ Cache
9
+ end #/ Sinatra
10
+
11
+ %w(templates output cache/helpers).each do |lib|
12
+ require "sinatra/#{lib}"
13
+ end
@@ -0,0 +1,663 @@
1
+
2
+ module Sinatra
3
+
4
+ # = Sinatra::Cache
5
+ #
6
+ # A Sinatra Extension that makes Page and Fragment Caching easy wthin your Sinatra apps.
7
+ #
8
+ # == Installation
9
+ #
10
+ # # Add RubyGems.org (former Gemcutter) to your RubyGems sources
11
+ # $ gem sources -a http://rubygems.org
12
+ #
13
+ # $ (sudo)? gem install sinatra-cache
14
+ #
15
+ # == Dependencies
16
+ #
17
+ # This Gem depends upon the following:
18
+ #
19
+ # === Runtime:
20
+ #
21
+ # * sinatra ( >= 1.0.a )
22
+ #
23
+ #
24
+ # === Development & Tests:
25
+ #
26
+ # * rspec (>= 1.3.0 )
27
+ # * rack-test (>= 0.5.3)
28
+ # * rspec_hpricot_matchers (>= 0.1.0)
29
+ # * sinatra-tests (>= 0.1.6)
30
+ # * fileutils
31
+ # * sass
32
+ # * ostruct
33
+ # * yaml
34
+ # * json
35
+ #
36
+ #
37
+ # == Getting Started
38
+ #
39
+ # To start caching your app's ouput, just require and register
40
+ # the extension in your sub-classed Sinatra app:
41
+ #
42
+ # require 'sinatra/cache'
43
+ #
44
+ # class YourApp < Sinatra::Base
45
+ #
46
+ # # NB! you need to set the root of the app first
47
+ # set :root, '/path/2/the/root/of/your/app'
48
+ #
49
+ # register(Sinatra::Cache)
50
+ #
51
+ # set :cache_enabled, true # turn it on
52
+ #
53
+ # <snip...>
54
+ #
55
+ # end
56
+ #
57
+ #
58
+ # That's more or less it.
59
+ #
60
+ # You should now be caching your output by default, in <tt>:production</tt> mode, as long as you use
61
+ # one of Sinatra's render methods:
62
+ #
63
+ # erb(), erubis(), haml(), sass(), builder(), etc..
64
+ #
65
+ # ...or any render method that uses <tt>Sinatra::Templates#render()</tt> as its base.
66
+ #
67
+ #
68
+ #
69
+ # == Configuration Settings
70
+ #
71
+ # The default settings should help you get moving quickly, and are fairly common sense based.
72
+ #
73
+ #
74
+ # === <tt>:cache_enabled</tt>
75
+ #
76
+ # This setting toggles the cache functionality On / Off.
77
+ # Default is: <tt>false</tt>
78
+ #
79
+ #
80
+ # === <tt>:cache_environment</tt>
81
+ #
82
+ # Sets the environment during which the cache functionality is active.
83
+ # Default is: <tt>:production</tt>
84
+ #
85
+ #
86
+ # === <tt>:cache_page_extension</tt>+
87
+ #
88
+ # Sets the default file extension for cached files.
89
+ # Default is: <tt>.html</tt>
90
+ #
91
+ #
92
+ # === <tt>:cache_output_dir</tt>
93
+ #
94
+ # Sets cache directory where the cached files are stored.
95
+ # Default is: == "/path/2/your/app/public"
96
+ #
97
+ # Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
98
+ # if you can get that to work with your webserver setup.
99
+ #
100
+ #
101
+ # === <tt>:cache_fragments_output_dir</tt>
102
+ #
103
+ # Sets the directory where cached fragments are stored.
104
+ # Default is the '../tmp/cache_fragments/' directory at the root of your app.
105
+ #
106
+ # This is for security reasons since you don't really want your cached fragments publically available.
107
+ #
108
+ #
109
+ # === <tt>:cache_fragments_wrap_with_html_comments</tt>
110
+ #
111
+ # This setting toggles the wrapping of cached fragments in HTML comments. (see below)
112
+ # Default is: <tt>true</tt>
113
+ #
114
+ #
115
+ # === <tt>:cache_logging</tt>
116
+ #
117
+ # This setting toggles the logging of various cache calls. If the app has access to the <tt>#logger</tt> method,
118
+ # curtesy of Sinatra::Logger[http://github.com/kematzy/sinatra-logger] then it will log there, otherwise logging
119
+ # is silent.
120
+ #
121
+ # Default is: <tt>true</tt>
122
+ #
123
+ #
124
+ # === <tt>:cache_logging_level</tt>
125
+ #
126
+ # Sets the level at which the cache logger should log it's messages.
127
+ # Default is: <tt>:info</tt>
128
+ #
129
+ # Available options are: [:fatal, :error, :warn, :info, :debug]
130
+ #
131
+ #
132
+ # == Basic Page Caching
133
+ #
134
+ # By default caching only happens in <tt>:production</tt> mode, and via the Sinatra render methods, erb(), etc,
135
+ #
136
+ # So asuming we have the following setup (continued from above)
137
+ #
138
+ #
139
+ # class YourApp
140
+ #
141
+ # <snip...>
142
+ #
143
+ # set :cache_output_dir, "/full/path/2/app/root/public/system/cache"
144
+ #
145
+ # <snip...>
146
+ #
147
+ # get('/') { erb(:index) } # => is cached as '../index.html'
148
+ #
149
+ # get('/contact') { erb(:contact) } # => is cached as '../contact.html'
150
+ #
151
+ # # NB! the trailing slash on the URL
152
+ # get('/about/') { erb(:about) } # => is cached as '../about/index.html'
153
+ #
154
+ # get('/feed.rss') { builder(:feed) } # => is cached as '../feed.rss'
155
+ # # NB! uses the extension of the passed URL,
156
+ # # but DOES NOT ensure the format of the content based on the extension provided.
157
+ #
158
+ # # complex URL with multiple possible params
159
+ # get %r{/articles/?([\s\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?} do
160
+ # erb(:articles)
161
+ # end
162
+ # # with the '/articles/a/b/c => is cached as ../articles/a/b/c.html
163
+ #
164
+ # # NB! the trailing slash on the URL
165
+ # # with the '/articles/a/b/c/ => is cached as ../articles/a/b/c/index.html
166
+ #
167
+ # # CSS caching via Sass # => is cached as '.../css/screen.css'
168
+ # get '/css/screen.css' do
169
+ # content_type 'text/css'
170
+ # sass(:'css/screen')
171
+ # end
172
+ #
173
+ # # to turn off caching on certain pages.
174
+ # get('/dont/cache/this/page') { erb(:aview, :cache => false) } # => is NOT cached
175
+ #
176
+ #
177
+ # # NB! any query string params - [ /?page=X&id=y ] - are stripped off and TOTALLY IGNORED
178
+ # # during the caching process.
179
+ #
180
+ # end
181
+ #
182
+ # OK, that's about all you need to know about basic Page Caching right there. Read the above example
183
+ # carefully until you understand all the variations.
184
+ #
185
+ #
186
+ # == Fragment Caching
187
+ #
188
+ # If you just need to cache a fragment of a page, then you would do as follows:
189
+ #
190
+ # class YourApp
191
+ #
192
+ # set :cache_fragments_output_dir, "/full/path/2/fragments/store/location"
193
+ #
194
+ # end
195
+ #
196
+ # Then in your views / layouts add the following:
197
+ #
198
+ # <% cache_fragment(:name_of_fragment) do %>
199
+ # # do something worth caching
200
+ # <% end %>
201
+ #
202
+ #
203
+ # Each fragment is stored in the same directory structure as your request
204
+ # so, if you have a request like this:
205
+ #
206
+ # get '/articles/2010/02' ...
207
+ #
208
+ # ...the cached fragment will be stored as:
209
+ #
210
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
211
+ #
212
+ # This enables you to use similar names for your fragments or have
213
+ # multiple URLs use the same view / layout.
214
+ #
215
+ #
216
+ # === An important limitation
217
+ #
218
+ # The fragment caching is dependent upon the final URL, so in the case of
219
+ # a blog, where each article uses the same view, but through different URLs,
220
+ # each of the articles would cache it's own fragment, which is ineffecient.
221
+ #
222
+ # To sort-of deal with this limitation I have temporarily added a very hackish
223
+ # 'fix' through adding a 2nd parameter (see example below), which will remove the
224
+ # last part of the URL and use the rest of the URL as the stored fragment path.
225
+ #
226
+ # So given the URL:
227
+ #
228
+ # get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
229
+ #
230
+ # and the following <tt>#cache_fragment</tt> declaration in your view
231
+ #
232
+ # <% cache_fragment(:name_of_fragment, :shared) do %>
233
+ # # do something worth caching
234
+ # <% end %>
235
+ #
236
+ # ...the cached fragment would be stored as:
237
+ #
238
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
239
+ #
240
+ # Any other URLs with the same URL root, like...
241
+ #
242
+ # get '/articles/2010/02/writing-sinatra-extensions' ...
243
+ #
244
+ # ... would use the same cached fragment.
245
+ #
246
+ #
247
+ # <b>NB!</b> currently only supports one level, but Your fork might fix that ;-)
248
+ #
249
+ #
250
+ # == Cache Expiration
251
+ #
252
+ # <b>Under development, and not entirely final.</b> See Todo's below for more info.
253
+ #
254
+ #
255
+ # To expire a cached item - file or fragment - you use the :cache_expire() method.
256
+ #
257
+ #
258
+ # cache_expire('/contact') => expires ../contact.html
259
+ #
260
+ #
261
+ # # NB! notice the trailing slash
262
+ # cache_expire('/contact/') => expires ../contact/index.html
263
+ #
264
+ #
265
+ # cache_expire('/feed.rss') => expires ../feed.rss
266
+ #
267
+ #
268
+ # To expire a cached fragment:
269
+ #
270
+ # cache_expire('/some/path', :fragment => :name_of_fragment )
271
+ #
272
+ # => expires ../some/path/:name_of_fragment.html
273
+ #
274
+ #
275
+ #
276
+ # == A few important points to consider
277
+ #
278
+ #
279
+ # === The DANGERS of URL query string params
280
+ #
281
+ # By default the caching ignores the query string params, but that's not the only problem with query params.
282
+ #
283
+ # Let's say you have a URL like this:
284
+ #
285
+ # /products/?product_id=111
286
+ #
287
+ # and then inside that template [ .../views/products.erb ], you use the <tt>params[:product_id]</tt>
288
+ # param passed in for some purpose.
289
+ #
290
+ # <ul>
291
+ # <li>Product ID: <%= params[:product_id] %></li> # => 111
292
+ # ...
293
+ # </ul>
294
+ #
295
+ # If you cache this URL, then the cached file [ ../cache/products.html ] will be stored with that
296
+ # value embedded. Obviously not ideal for any other similar URLs with different <tt>product_id</tt>'s
297
+ #
298
+ # To overcome this issue, use either of these two methods.
299
+ #
300
+ # # in your_app.rb
301
+ #
302
+ # # turning off caching on this page
303
+ #
304
+ # get '/products/' do
305
+ # ...
306
+ # erb(:products, :cache => false)
307
+ # end
308
+ #
309
+ # # or
310
+ #
311
+ # # rework the URLs to something like '/products/111 '
312
+ #
313
+ # get '/products/:product_id' do
314
+ # ...
315
+ # erb(:products)
316
+ # end
317
+ #
318
+ #
319
+ #
320
+ # Thats's about all the information you need to know.
321
+ #
322
+ #
323
+ module Cache
324
+
325
+ module Helpers
326
+
327
+ ##
328
+ # This method either caches the code fragment and then renders it,
329
+ # or locates the cached fragement and renders that.
330
+ #
331
+ # By default the cached fragement is stored in the ../tmp/cache_fragments/
332
+ # directory at the root of your app.
333
+ #
334
+ # Each fragment is stored in the same directory structure as your request
335
+ # so, if you have a request like this:
336
+ #
337
+ # get '/articles/2010/02' ...
338
+ #
339
+ # ...the cached fragment will be stored as:
340
+ #
341
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
342
+ #
343
+ # This enables you to use similar names for your fragments or have
344
+ # multiple URLs use the same view / layout.
345
+ #
346
+ # ==== Examples
347
+ #
348
+ # <% cache_fragment(:name_of_fragment) do %>
349
+ # # do something worth caching
350
+ # <% end %>
351
+ #
352
+ # === GOTCHA
353
+ #
354
+ # The fragment caching is dependent upon the final URL, so in the case of
355
+ # a blog, where each article uses the same view, but through different URLs,
356
+ # each of the articles would cache it's own fragment.
357
+ #
358
+ # To sort-of deal with this limitation I have added a very hackish 'fix'
359
+ # through adding a 2nd parameter (see example below), which will
360
+ # remove the last part of the URL and use the rest of the URL as
361
+ # the stored fragment path.
362
+ #
363
+ # ==== Example
364
+ #
365
+ # Given the URL:
366
+ #
367
+ # get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
368
+ #
369
+ # and the following <tt>#cache_fragment</tt> declaration in your view
370
+ #
371
+ # <% cache_fragment(:name_of_fragment, :shared) do %>
372
+ # # do something worth caching
373
+ # <% end %>
374
+ #
375
+ # ...the cached fragment would be stored as:
376
+ #
377
+ # ../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
378
+ #
379
+ # Any other URLs with the same URL root, like...
380
+ #
381
+ # get '/articles/2010/02/writing-sinatra-extensions' ...
382
+ #
383
+ # ... would use the same cached fragment.
384
+ #
385
+ # @api public
386
+ def cache_fragment(fragment_name, shared = nil, &block)
387
+ # 1. check for a block, there must always be a block
388
+ raise ArgumentError, "Missing block" unless block_given?
389
+
390
+ # 2. get the fragment path, by combining the PATH_INFO of the request, and the fragment_name
391
+ dir_structure = request.path_info.empty? ? '' : request.path_info.gsub(/^\//,'').gsub(/\/$/,'')
392
+ # if we are sharing this fragment with other URLs (as in the all the articles in a category of a blog)
393
+ # then lob off the last part of the URL
394
+ unless shared.nil?
395
+ dirs = dir_structure.split('/')
396
+ dir_structure = dirs.first(dirs.length-1).join('/')
397
+ end
398
+ cf = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{fragment_name}.html"
399
+ # 3. ensure the fragment cache directory exists for this fragment
400
+ FileUtils.mkdir_p(File.dirname(cf)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cf)} ]"
401
+
402
+ # 3. check if the fragment is already cached ?
403
+ if test(?f, cf)
404
+ # 4. yes. cached, so load it up into the ERB buffer . Sorry, don't know how to do this for Haml or any others.
405
+ block_content = IO.read(cf)
406
+ else
407
+ # 4. not cached, so process the block and then cache it
408
+ block_content = capture_html(&block) if block_given?
409
+ # 5. add some timestamp comments around the fragment, if the end user wants it
410
+ if settings.cache_fragments_wrap_with_html_comments
411
+ content_2_cache = "<!-- cache fragment: #{dir_structure}/#{fragment_name} -->"
412
+ content_2_cache << block_content
413
+ content_2_cache << "<!-- /cache fragment: #{fragment_name} cached at [ #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] -->\n"
414
+ else
415
+ content_2_cache << block_content
416
+ end
417
+ # 6. write it to cache
418
+ cache_write_file(cf, content_2_cache)
419
+ end
420
+ # 5. 'return' the content by
421
+ block_is_template?(block) ? concat_content(block_content) : block_content
422
+ end
423
+ # for future versions once old habits are gone
424
+ # alias_method :cache, :cache_fragment
425
+
426
+
427
+ ##
428
+ # <b>NB!! Deprecated method.</b>
429
+ #
430
+ # Just returns the content after throwing out a warning.
431
+ #
432
+ def cache(content, opts={})
433
+ warn("Deprecated method, caching is now happening by default if the :cache_enabled option is true")
434
+ content
435
+ end
436
+
437
+
438
+ ##
439
+ # Expires the cached file (page) or fragment.
440
+ #
441
+ # ==== Examples
442
+ #
443
+ # cache_expire('/contact') => expires ../contact.html
444
+ #
445
+ # cache_expire('/feed.rss') => expires ../feed.rss
446
+ #
447
+ # To expire a cached fragment:
448
+ #
449
+ # cache_expire('/some/path', :fragment => :name_of_fragment )
450
+ # => expires ../some/path/:name_of_fragment.html
451
+ #
452
+ #
453
+ # @api public
454
+ def cache_expire(path, options={})
455
+ # 1. bail quickly if we don't have caching enabled
456
+ return unless settings.cache_enabled
457
+ options = { :fragment => false }.merge(options)
458
+
459
+ if options[:fragment] # dealing with a fragment
460
+ dir_structure = path.gsub(/^\//,'').gsub(/\/$/,'')
461
+ file_path = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{options[:fragment]}.html"
462
+ else
463
+ file_path = cache_file_path(path)
464
+ end
465
+
466
+ if test(?f, file_path)
467
+ File.delete(file_path)
468
+ log(:info,"Expired [#{file_path.sub(settings.root,'')}] successfully")
469
+ else
470
+ log(:warn,"The cached file [#{file_path}] could NOT be expired as it was NOT found")
471
+ end
472
+ end
473
+
474
+
475
+ ##
476
+ # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
477
+ #
478
+ # *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
479
+ # inside the <head> tag. Many hours wasted on that lesson ;-)
480
+ #
481
+ # ==== Examples
482
+ #
483
+ # <%= cache_timestamp %> # => <!-- page cached: 2009-12-21 12:00:00 -->
484
+ #
485
+ # @api public
486
+ def cache_timestamp
487
+ if settings.cache_enabled && settings.cache_environment == settings.environment
488
+ "<!-- page cached: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} -->\n"
489
+ end
490
+ end
491
+ # backwards compat and syntactic sugar for others
492
+ alias_method :cache_page_timestamp, :cache_timestamp
493
+ alias_method :page_cached_at, :cache_timestamp
494
+
495
+
496
+ ## PRIVATE METHODS
497
+ private
498
+
499
+ ##
500
+ # Converts the PATH_INFO path into the full cached file path.
501
+ #
502
+ # ==== GOTCHA:
503
+ #
504
+ # <b>NB!</b> completely ignores the URL query params passed such as
505
+ # in this example:
506
+ #
507
+ # /products?page=2
508
+ #
509
+ # To capture and cache those query strings, please do as follows:
510
+ #
511
+ # get 'products/page/:page' { ... } # in your Sinatra app
512
+ #
513
+ # /products/page/2 => .../public/cache/products/page/2.html
514
+ #
515
+ #
516
+ # ==== Examples
517
+ #
518
+ # / => .../public/cache/index.html
519
+ #
520
+ # /contact => .../public/cache/contact.html
521
+ #
522
+ # /contact/ => .../public/cache/contact/index.html
523
+ #
524
+ #
525
+ # @api public
526
+ def cache_file_path(in_path = nil)
527
+ path = settings.send(:cache_output_dir).dup
528
+
529
+
530
+ path_info = in_path.nil? ? request.path_info : in_path
531
+ if (path_info.empty? || path_info == "/" )
532
+ path << "/index"
533
+ elsif ( path_info[-1, 1] == '/' )
534
+ path << ::Rack::Utils.unescape(path_info.chomp('/') << '/index')
535
+ else
536
+ path << ::Rack::Utils.unescape(path_info.chomp('/'))
537
+ end
538
+ path << settings.cache_page_extension if File.extname(path) == ''
539
+ return path
540
+ end
541
+
542
+ ##
543
+ # Writes the cached file to disk, only during GET requests,
544
+ # and then returns the content.
545
+ #
546
+ # ==== Examples
547
+ #
548
+ #
549
+ # @api private
550
+ def cache_write_file(cache_file, content)
551
+ # only cache GET request [http://rack.rubyforge.org/doc/classes/Rack/Request.html#M000239]
552
+ if request.get?
553
+ FileUtils.mkdir_p(File.dirname(cache_file)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cache_file)} ]"
554
+ File.open(cache_file, 'wb'){ |f| f << content}
555
+ end
556
+ return content
557
+ end
558
+
559
+ ##
560
+ # Establishes the file name of the cached file from the path given
561
+ #
562
+ # @api private
563
+ def cache_file_name(path, options={})
564
+ name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
565
+ name << settings.cache_page_extension unless (name.split('/').last || name).include? '.'
566
+ return name
567
+ end
568
+
569
+ ##
570
+ # Sets the full path to the cached page/file
571
+ # Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
572
+ #
573
+ #
574
+ # @api private
575
+ def cache_page_path(path, options={})
576
+ # test if given a full path rather than relative path, otherwise join the public path to cache_dir
577
+ # and ensure it is a full path
578
+ cache_dir = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ?
579
+ settings.cache_output_dir : File.expand_path("#{settings.public}/#{settings.cache_output_dir}")
580
+ cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
581
+ "#{cache_dir}/#{cache_file_name(path, options)}"
582
+ end
583
+
584
+ ##
585
+ # Convenience method that handles logging of Cache related stuff.
586
+ #
587
+ # Uses Sinatra::Logger's #logger method if available, otherwise just
588
+ # puts out the log message.
589
+ #
590
+ def log(scope, msg)
591
+ if settings.cache_logging
592
+ if scope.to_sym == settings.cache_logging_level.to_sym
593
+ if self.respond_to?(:logger)
594
+ logger.send(scope, msg)
595
+ else
596
+ puts "#{scope.to_s.upcase}: #{msg}"
597
+ end
598
+ end
599
+ end
600
+ end
601
+
602
+
603
+ end #/ Helpers
604
+
605
+
606
+ ##
607
+ # The default options:
608
+ #
609
+ # * +:cache_enabled+ => toggle for the cache functionality. Default is: +false+
610
+ #
611
+ # * +:cache_environment+ => sets the environment during which to cache. Default is: +:production+
612
+ #
613
+ # * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
614
+ #
615
+ # * +:cache_output_dir+ => sets cache directory where cached files are stored. Default is: == "/path/2/your/app/public"
616
+ # Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
617
+ # if you can get that to work with your webserver setup.
618
+ #
619
+ # * +:cache_fragments_output_dir+ => sets the directory where cached fragments are stored.
620
+ # Default is the '../tmp/cache_fragments/' directory at the root of your app.
621
+ #
622
+ # * +:cache_fragments_wrap_with_html_comments+ => toggle for wrapping the cached fragment in HTML comments.
623
+ # Default is: +true+
624
+ #
625
+ # * +:cache_logging+ => toggle for logging the cache calls. Default is: +true+
626
+ #
627
+ # * +:cache_logging_level+ => sets the level of the cache logger. Default is: <tt>:info</tt>.<br>
628
+ # Available options are: [:fatal, :error, :warn, :info, :debug]
629
+ #
630
+ #
631
+ def self.registered(app)
632
+ app.helpers Sinatra::Output::Helpers
633
+ app.helpers Cache::Helpers
634
+
635
+ ## CONFIGURATIONS::
636
+ app.set :cache_enabled, false
637
+ app.set :cache_environment, :production
638
+ app.set :cache_page_extension, '.html'
639
+ app.set :cache_output_dir, lambda { app.public }
640
+ app.set :cache_fragments_output_dir, lambda { "#{app.root}/tmp/cache_fragments" }
641
+ app.set :cache_fragments_wrap_with_html_comments, true
642
+
643
+ app.set :cache_logging, true
644
+ app.set :cache_logging_level, :info
645
+
646
+
647
+ ## add the extension specific options to those inspectable by :settings_inspect method
648
+ if app.respond_to?(:sinatra_settings_for_inspection)
649
+ %w( cache_enabled cache_environment cache_page_extension cache_output_dir
650
+ cache_fragments_output_dir cache_fragments_wrap_with_html_comments
651
+ cache_logging cache_logging_level
652
+ ).each do |m|
653
+ app.sinatra_settings_for_inspection << m
654
+ end
655
+ end
656
+
657
+ end #/ self.registered
658
+
659
+ end #/ Cache
660
+
661
+ # register(Sinatra::Cache) # not really needed here
662
+
663
+ end #/ Sinatra