benjaminjackson-sinatra-cache 0.3.8

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